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.
@@ -1,22 +1,24 @@
1
- class Lennarb
1
+ module Lennarb
2
+ # Response object
3
+ #
2
4
  class Response
3
5
  # @!attribute [rw] status
4
- # @returns [Integer]
6
+ # @return [Integer]
5
7
  #
6
8
  attr_accessor :status
7
9
 
8
10
  # @!attribute [r] body
9
- # @returns [Array]
11
+ # @retrn [Array]
10
12
  #
11
13
  attr_reader :body
12
14
 
13
15
  # @!attribute [r] headers
14
- # @returns [Hash]
16
+ # @retrn [Hash]
15
17
  #
16
18
  attr_reader :headers
17
19
 
18
20
  # @!attribute [r] length
19
- # @returns [Integer]
21
+ # @retrn [Integer]
20
22
  #
21
23
  attr_reader :length
22
24
 
@@ -31,13 +33,12 @@ class Lennarb
31
33
  CONTENT_LENGTH = "content-length"
32
34
  private_constant :CONTENT_LENGTH
33
35
 
34
- ContentType = {HTML: "text/html", TEXT: "text/plain", JSON: "application/json"}.freeze
35
36
  # Initialize the response object
36
37
  #
37
- # @returns [Response]
38
+ # @retrn [Response]
38
39
  #
39
40
  def initialize
40
- @status = 404
41
+ @status = 200
41
42
  @headers = {}
42
43
  @body = []
43
44
  @length = 0
@@ -45,9 +46,9 @@ class Lennarb
45
46
 
46
47
  # Set the response header
47
48
  #
48
- # @parameter [String] key
49
+ # @param [String] key
49
50
  #
50
- # @returns [String] value
51
+ # @retrn [String] value
51
52
  #
52
53
  def [](key)
53
54
  @headers[key]
@@ -55,10 +56,10 @@ class Lennarb
55
56
 
56
57
  # Get the response header
57
58
  #
58
- # @parameter [String] key
59
- # @parameter [String] value
59
+ # @param [String] key
60
+ # @param [String] value
60
61
  #
61
- # @returns [String] value
62
+ # @retrn [String] value
62
63
  #
63
64
  def []=(key, value)
64
65
  @headers[key] = value
@@ -66,9 +67,9 @@ class Lennarb
66
67
 
67
68
  # Write to the response body
68
69
  #
69
- # @parameter [String] str
70
+ # @param [String] str
70
71
  #
71
- # @returns [String] str
72
+ # @retrn [String] str
72
73
  #
73
74
  def write(str)
74
75
  str = str.to_s
@@ -79,41 +80,46 @@ class Lennarb
79
80
 
80
81
  # Set the response type to text
81
82
  #
82
- # @parameter [String] str
83
+ # @param [String] str
83
84
  #
84
- # @returns [String] str
85
+ # @retrn [String] str
85
86
  #
86
87
  def text(str)
87
- @headers[CONTENT_TYPE] = ContentType[:TEXT]
88
+ @headers[CONTENT_TYPE] = Lennarb::CONTENT_TYPE[:TEXT]
88
89
  write(str)
89
90
  end
90
91
 
91
92
  # Set the response type to html
92
93
  #
93
- # @parameter [String] str
94
+ # @param [String] str
94
95
  #
95
- # @returns [String] str
96
+ # @retrn [String] str
96
97
  #
97
98
  def html(str)
98
- @headers[CONTENT_TYPE] = ContentType[:HTML]
99
+ @headers[CONTENT_TYPE] = Lennarb::CONTENT_TYPE[:HTML]
99
100
  write(str)
100
101
  end
101
102
 
102
103
  # Set the response type to json
103
104
  #
104
- # @parameter [String] str
105
+ # @param [String] str
105
106
  #
106
- # @returns [String] str
107
+ # @retrn [String] str
107
108
  #
108
109
  def json(str)
109
- @headers[CONTENT_TYPE] = ContentType[:JSON]
110
- write(str)
110
+ json_str = JSON.generate(str)
111
+ @headers[CONTENT_TYPE] = Lennarb::CONTENT_TYPE[:JSON]
112
+ write(json_str)
113
+ rescue JSON::GeneratorError => e
114
+ @status = 500
115
+ @headers[CONTENT_TYPE] = Lennarb::CONTENT_TYPE[:TEXT]
116
+ write("JSON generation error: #{e.message}")
111
117
  end
112
118
 
113
119
  # Redirect the response
114
120
  #
115
- # @parameter [String] path
116
- # @parameter [Integer] status, default: 302
121
+ # @param [String] path
122
+ # @param [Integer] status, default: 302
117
123
  #
118
124
  def redirect(path, status = 302)
119
125
  @headers[LOCATION] = path
@@ -124,7 +130,7 @@ class Lennarb
124
130
 
125
131
  # Finish the response
126
132
  #
127
- # @returns [Array] response
133
+ # @retrn [Array] response
128
134
  #
129
135
  def finish
130
136
  [@status, @headers, @body]
@@ -1,7 +1,20 @@
1
- class Lennarb
1
+ module Lennarb
2
+ # RouteNode is key to the routing system. It is a tree structure that
3
+ # represents the routes of the application.
4
+ #
5
+ # @example
6
+ # node = RouteNode.new
7
+ # node.add_route(["foo", "bar"], :GET, -> {})
8
+ #
9
+ # @note RouteNode use a trie data structure to store the routes and match them.
10
+ #
2
11
  class RouteNode
3
12
  attr_accessor :static_children, :dynamic_children, :blocks, :param_key
4
13
 
14
+ # Initialize the route node.
15
+ #
16
+ # @retrn [RouteNode]
17
+ #
5
18
  def initialize
6
19
  @blocks = {}
7
20
  @param_key = nil
@@ -9,6 +22,14 @@ class Lennarb
9
22
  @dynamic_children = {}
10
23
  end
11
24
 
25
+ # Add a route to the route node.
26
+ #
27
+ # @param parts [Array<String>] The parts of the route.
28
+ # @param http_method [String] The HTTP method.
29
+ # @param block [Proc] The block to be executed when the route is matched.
30
+ #
31
+ # @retrn [void]
32
+ #
12
33
  def add_route(parts, http_method, block)
13
34
  current_node = self
14
35
 
@@ -28,6 +49,14 @@ class Lennarb
28
49
  current_node.blocks[http_method] = block
29
50
  end
30
51
 
52
+ # Match a route.
53
+ #
54
+ # @param parts [Array<String>] The parts of the route.
55
+ # @param http_method [String] The HTTP method.
56
+ # @param params [Hash] The parameters of the route.
57
+ #
58
+ # @retrn [Array<Proc, Hash>]
59
+ #
31
60
  def match_route(parts, http_method, params: {})
32
61
  if parts.empty?
33
62
  return [blocks[http_method], params] if blocks[http_method]
@@ -52,10 +81,35 @@ class Lennarb
52
81
  [nil, nil]
53
82
  end
54
83
 
84
+ # Merge another route node into this one.
85
+ #
86
+ # @param other [RouteNode] The other route node.
87
+ #
88
+ # @retrn [void|DuplicateRouteError]
89
+ #
55
90
  def merge!(other)
56
- static_children.merge!(other.static_children)
57
- dynamic_children.merge!(other.dynamic_children)
58
- blocks.merge!(other.blocks)
91
+ other.blocks.each do |http_method, block|
92
+ if @blocks[http_method]
93
+ raise Lennarb::DuplicateRouteError, "Duplicate route for HTTP method: #{http_method}"
94
+ end
95
+ @blocks[http_method] = block
96
+ end
97
+
98
+ other.static_children.each do |path, node|
99
+ if @static_children[path]
100
+ @static_children[path].merge!(node)
101
+ else
102
+ @static_children[path] = node
103
+ end
104
+ end
105
+
106
+ other.dynamic_children.each do |param, node|
107
+ if @dynamic_children[param]
108
+ @dynamic_children[param].merge!(node)
109
+ else
110
+ @dynamic_children[param] = node
111
+ end
112
+ end
59
113
  end
60
114
  end
61
115
  end
@@ -0,0 +1,67 @@
1
+ module Lennarb
2
+ # Routes class for managing application routes
3
+ #
4
+ class Routes
5
+ # Initialize a new Routes instance
6
+ #
7
+ # @return [Routes] The initialized routes instance
8
+ def initialize
9
+ @store = RouteNode.new
10
+ @frozen = false
11
+ end
12
+
13
+ # Define a route for each HTTP method
14
+ HTTP_METHODS.each do |http_method|
15
+ define_method(http_method.downcase) do |path, &block|
16
+ fail RoutesFrozenError, "Routes are frozen and cannot be modified" if @frozen
17
+ register_route(http_method, path, &block)
18
+ end
19
+ end
20
+
21
+ # Define the root route (GET /)
22
+ #
23
+ # @param block [Proc] Block to execute when route matches
24
+ # @return [void]
25
+ def root(&block)
26
+ get("/", &block)
27
+ end
28
+
29
+ # Match a route with the given path parts and HTTP method
30
+ #
31
+ # @param parts [Array<String>] Path parts
32
+ # @param http_method [Symbol] HTTP method
33
+ # @return [Array(Proc, Hash), nil] Route handler and params, or nil if no match
34
+ def match_route(parts, http_method)
35
+ @store.match_route(parts, http_method)
36
+ end
37
+
38
+ # Freeze the routes to prevent further modification
39
+ #
40
+ # @return [self] The frozen routes
41
+ def freeze
42
+ @frozen = true
43
+ @store.freeze
44
+ self
45
+ end
46
+
47
+ # Check if the routes are frozen
48
+ #
49
+ # @return [Boolean] True if frozen
50
+ def frozen?
51
+ @frozen
52
+ end
53
+
54
+ private
55
+
56
+ # Register a route with the specified HTTP method and path
57
+ #
58
+ # @param http_method [Symbol] HTTP method (:GET, :POST, etc.)
59
+ # @param path [String] Route path pattern
60
+ # @param block [Proc] Block to execute when route matches
61
+ # @return [void]
62
+ def register_route(http_method, path, &block)
63
+ parts = path.split("/").reject(&:empty?)
64
+ @store.add_route(parts, http_method, block)
65
+ end
66
+ end
67
+ end
@@ -1,5 +1,3 @@
1
- class Lennarb
2
- VERSION = "1.4.0"
3
-
4
- public_constant :VERSION
1
+ module Lennarb # :nodoc:
2
+ VERSION = "1.5.0" # :nodoc:
5
3
  end
data/lib/lennarb.rb CHANGED
@@ -1,72 +1,40 @@
1
1
  # Core extensions
2
2
  #
3
- require "pathname"
4
- require "rack"
5
3
  require "bundler"
4
+ require "rack"
5
+ require "json"
6
+ require "pathname"
7
+ require "logger"
8
+ require "securerandom"
9
+ require "colorize"
10
+ require "superconfig"
6
11
 
7
- require_relative "lennarb/request"
8
- require_relative "lennarb/response"
9
- require_relative "lennarb/route_node"
10
- require_relative "lennarb/version"
11
- require_relative "lennarb/constansts"
12
-
13
- class Lennarb
14
- class LennarbError < StandardError; end
15
-
16
- def initialize
17
- @_mutex ||= Mutex.new
18
- yield self if block_given?
19
- end
20
-
21
- HTTP_METHODS.each do |http_method|
22
- define_method(http_method.downcase) do |path, &block|
23
- add_route(path, http_method, block)
24
- end
25
- end
26
-
27
- def root
28
- @root ||= RouteNode.new
29
- end
30
-
31
- def app
32
- @app ||= begin
33
- request_handler = ->(env) { process_request(env) }
34
-
35
- Rack::Builder.app do
36
- run request_handler
37
- end
38
- end
39
- end
40
-
41
- def initializer!
42
- Bundler.require(:default, ENV["LENNA_ENV"] || "development")
43
-
44
- root.freeze
45
- app.freeze
46
- end
47
-
48
- def call(env) = @_mutex.synchronize { app.call(env) }
49
-
50
- def add_route(path, http_method, block)
51
- parts = path.split("/").reject(&:empty?)
52
- root.add_route(parts, http_method, block)
53
- end
54
-
55
- private def process_request(env)
56
- http_method = env[Rack::REQUEST_METHOD].to_sym
57
- parts = env[Rack::PATH_INFO].split("/").reject(&:empty?)
58
-
59
- block, params = root.match_route(parts, http_method)
60
- return [404, {"content-type" => Response::ContentType[:TEXT]}, ["Not Found"]] unless block
61
-
62
- res = Response.new
63
- req = Request.new(env, params)
64
-
65
- catch(:halt) do
66
- instance_exec(req, res, &block)
67
- res.finish
68
- end
69
- rescue => e
70
- [500, {"content-type" => Response::ContentType[:TEXT]}, ["Internal Server Error - #{e.message}"]]
71
- end
12
+ # Lennarb is a lightweight Ruby web framework based on Rack that offers a high-performance solution for web development. Focused on simplicity, Lennarb delivers essential functionality without unnecessary complexity.
13
+ # Available in two versions:
14
+ #
15
+ # Lennarb::App: Minimal version. Includes only the core components, such as request, response, and route handling.
16
+ #
17
+ # Lennarb::Application: Standard version. Include common features like middleware, request handler, and more.
18
+ #
19
+ module Lennarb
20
+ require_relative "lennarb/constants"
21
+ require_relative "lennarb/environment"
22
+ require_relative "lennarb/version"
23
+ require_relative "lennarb/request"
24
+ require_relative "lennarb/response"
25
+ require_relative "lennarb/route_node"
26
+ require_relative "lennarb/routes"
27
+ require_relative "lennarb/config"
28
+ require_relative "lennarb/errors"
29
+ require_relative "lennarb/middleware_stack"
30
+ require_relative "lennarb/helpers"
31
+ require_relative "lennarb/hooks"
32
+ require_relative "lennarb/request_handler"
33
+ require_relative "lennarb/base"
34
+ require_relative "lennarb/app"
35
+
36
+ # Middlewares
37
+ require_relative "lennarb/logger"
38
+ require_relative "lennarb/parameter_filter"
39
+ require_relative "lennarb/middleware/request_logger"
72
40
  end
data/logo/lennarb.svg ADDED
@@ -0,0 +1,11 @@
1
+ <svg width="192" height="56" viewBox="0 0 192 56" fill="none" xmlns="http://www.w3.org/2000/svg">
2
+ <rect x="142" y="30.9185" width="35.2401" height="35.2401" rx="5" transform="rotate(-45 142 30.9185)" fill="#E8E8E8"/>
3
+ <path d="M3.1075 3.4H14.3395V27.4H30.1315V37H3.1075V3.4ZM48.6719 21.64C48.5439 20.392 48.1439 19.432 47.4719 18.76C46.7999 18.056 45.9839 17.704 45.0239 17.704C44.0959 17.704 43.3279 18.056 42.7199 18.76C42.1119 19.464 41.7279 20.424 41.5679 21.64H48.6719ZM45.8879 37.624C43.7439 37.624 41.7759 37.304 39.9839 36.664C38.2239 35.992 36.7039 35.064 35.4239 33.88C34.1759 32.696 33.1999 31.272 32.4959 29.608C31.8239 27.944 31.4879 26.088 31.4879 24.04V23.944C31.4879 21.992 31.8239 20.184 32.4959 18.52C33.1679 16.824 34.0959 15.368 35.2799 14.152C36.4639 12.904 37.8719 11.928 39.5039 11.224C41.1679 10.52 42.9919 10.168 44.9759 10.168C47.3119 10.168 49.3279 10.552 51.0239 11.32C52.7519 12.056 54.1759 13.08 55.2959 14.392C56.4159 15.704 57.2479 17.24 57.7919 19C58.3359 20.76 58.6079 22.632 58.6079 24.616C58.6079 24.904 58.6079 25.208 58.6079 25.528C58.6079 25.848 58.5919 26.152 58.5599 26.44H41.7119C42.0959 27.592 42.7199 28.472 43.5839 29.08C44.4479 29.656 45.4719 29.944 46.6559 29.944C47.6479 29.944 48.5599 29.72 49.3919 29.272C50.2559 28.824 51.1199 28.152 51.9839 27.256L57.8399 31.912C56.5279 33.608 54.9119 34.984 52.9919 36.04C51.1039 37.096 48.7359 37.624 45.8879 37.624ZM60.9914 10.792H71.8874V14.44C72.3034 13.896 72.7834 13.368 73.3274 12.856C73.8714 12.344 74.4794 11.896 75.1514 11.512C75.8234 11.096 76.5434 10.776 77.3114 10.552C78.1114 10.296 78.9754 10.168 79.9034 10.168C82.6874 10.168 84.8794 11.016 86.4794 12.712C88.0794 14.408 88.8794 16.744 88.8794 19.72V37H77.9834V23.224C77.9834 22.104 77.6954 21.24 77.1194 20.632C76.5434 19.992 75.8394 19.672 75.0074 19.672C74.1754 19.672 73.4394 19.992 72.7994 20.632C72.1914 21.24 71.8874 22.104 71.8874 23.224V37H60.9914V10.792ZM91.9289 10.792H102.825V14.44C103.241 13.896 103.721 13.368 104.265 12.856C104.809 12.344 105.417 11.896 106.089 11.512C106.761 11.096 107.481 10.776 108.249 10.552C109.049 10.296 109.913 10.168 110.841 10.168C113.625 10.168 115.817 11.016 117.417 12.712C119.017 14.408 119.817 16.744 119.817 19.72V37H108.921V23.224C108.921 22.104 108.633 21.24 108.057 20.632C107.481 19.992 106.777 19.672 105.945 19.672C105.113 19.672 104.377 19.992 103.737 20.632C103.129 21.24 102.825 22.104 102.825 23.224V37H91.9289V10.792ZM134.77 31.336C135.73 31.336 136.53 30.984 137.17 30.28C137.81 29.544 138.13 28.552 138.13 27.304V26.2C137.81 26.072 137.426 25.976 136.978 25.912C136.53 25.816 136.082 25.768 135.634 25.768C134.546 25.768 133.714 26.056 133.138 26.632C132.562 27.176 132.274 27.864 132.274 28.696V28.792C132.274 29.56 132.514 30.184 132.994 30.664C133.474 31.112 134.066 31.336 134.77 31.336ZM130.45 37.624C129.202 37.624 128.05 37.448 126.994 37.096C125.938 36.744 125.026 36.232 124.258 35.56C123.49 34.856 122.898 34.008 122.482 33.016C122.066 32.024 121.858 30.872 121.858 29.56V29.464C121.858 26.84 122.754 24.824 124.546 23.416C126.338 22.008 128.77 21.304 131.842 21.304C132.386 21.304 132.962 21.336 133.57 21.4C134.178 21.432 134.754 21.496 135.298 21.592C135.874 21.688 136.402 21.8 136.882 21.928C137.362 22.024 137.746 22.136 138.034 22.264V21.832C138.034 20.616 137.65 19.688 136.882 19.048C136.114 18.376 134.882 18.04 133.186 18.04C131.842 18.04 130.578 18.184 129.394 18.472C128.242 18.728 127.09 19.08 125.938 19.528L124.018 12.28C125.554 11.704 127.202 11.24 128.962 10.888C130.722 10.536 132.754 10.36 135.058 10.36C137.746 10.36 139.97 10.648 141.73 11.224C143.522 11.768 144.994 12.616 146.146 13.768C147.138 14.76 147.842 15.912 148.258 17.224C148.674 18.536 148.882 20.152 148.882 22.072V37H137.986V34.36C137.058 35.352 135.97 36.152 134.722 36.76C133.506 37.336 132.082 37.624 130.45 37.624ZM150.944 31.48H156.512V37H150.944V31.48ZM157.933 23.896H163.381V26.536C163.749 25.656 164.253 24.952 164.893 24.424C165.533 23.896 166.397 23.656 167.485 23.704V29.488H166.909C165.757 29.488 164.877 29.824 164.269 30.496C163.677 31.152 163.381 32.208 163.381 33.664V37H157.933V23.896ZM175.922 32.824C176.546 32.824 177.066 32.6 177.482 32.152C177.898 31.704 178.106 31.144 178.106 30.472V30.424C178.106 29.752 177.898 29.192 177.482 28.744C177.066 28.296 176.546 28.072 175.922 28.072C175.298 28.072 174.778 28.296 174.362 28.744C173.946 29.192 173.738 29.752 173.738 30.424V30.472C173.738 30.792 173.794 31.096 173.906 31.384C174.018 31.672 174.17 31.928 174.362 32.152C174.554 32.36 174.778 32.528 175.034 32.656C175.306 32.768 175.602 32.824 175.922 32.824ZM177.794 37.312C176.802 37.312 176.002 37.128 175.394 36.76C174.786 36.392 174.274 35.976 173.858 35.512V37H168.41V19.48H173.858V25.504C174.082 25.264 174.314 25.032 174.554 24.808C174.81 24.568 175.09 24.36 175.394 24.184C175.698 24.008 176.042 23.864 176.426 23.752C176.81 23.64 177.25 23.584 177.746 23.584C178.53 23.584 179.266 23.752 179.954 24.088C180.642 24.408 181.242 24.872 181.754 25.48C182.266 26.072 182.666 26.784 182.954 27.616C183.242 28.432 183.386 29.344 183.386 30.352V30.4C183.386 31.44 183.234 32.384 182.93 33.232C182.642 34.08 182.242 34.808 181.73 35.416C181.234 36.024 180.642 36.496 179.954 36.832C179.282 37.152 178.562 37.312 177.794 37.312Z" fill="url(#paint0_linear_4_61)"/>
4
+ <defs>
5
+ <linearGradient id="paint0_linear_4_61" x1="0" y1="23" x2="185" y2="23" gradientUnits="userSpaceOnUse">
6
+ <stop stop-color="#FF793B"/>
7
+ <stop offset="1" stop-color="#FF3BF2"/>
8
+ </linearGradient>
9
+ </defs>
10
+ </svg>
11
+