lennarb 0.1.5 → 0.1.7

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,95 +1,93 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ # Released under the MIT License.
4
+ # Copyright, 2023, by Aristóteles Coutinho.
5
+
3
6
  require 'colorize'
4
7
 
5
8
  module Lenna
6
- module Middleware
7
- module Default
8
- # The Logging module is responsible for logging the requests.
9
- #
10
- # @api private
11
- #
12
- # @example:
13
- # Logging.call(req, res, next_middleware)
14
- # # => [2021-01-01 00:00:00 +0000] "GET /" 200 0.00ms
15
- module Logging
16
- extend self
9
+ module Middleware
10
+ module Default
11
+ # The Logging module is responsible for logging the requests.
12
+ #
13
+ # @private
14
+ #
15
+ module Logging
16
+ extend self
17
17
 
18
- # This method is used to log the request.
19
- #
20
- # @param req [Rack::Request ] The request
21
- # @param res [Rack::Response] The response
22
- # @param next_middleware [Proc] The next middleware
23
- def call(req, res, next_middleware)
24
- start_time = ::Time.now
25
- next_middleware.call
26
- end_time = ::Time.now
18
+ # This method is used to log the request.
19
+ #
20
+ # @parameter req [Rack::Request ] The request
21
+ # @parameter res [Rack::Response] The response
22
+ # @parameter next_middleware [Proc] The next middleware
23
+ #
24
+ def call(req, res, next_middleware)
25
+ start_time = ::Time.now
26
+ next_middleware.call
27
+ end_time = ::Time.now
27
28
 
28
- http_method = colorize_http_method(req.request_method)
29
- status_code = colorize_status_code(res.status.to_s)
30
- duration = calculate_duration(start_time, end_time)
29
+ http_method = colorize_http_method(req.request_method)
30
+ status_code = colorize_status_code(res.status.to_s)
31
+ duration = calculate_duration(start_time, end_time)
31
32
 
32
- log_message = "[#{start_time}] \"#{http_method} #{req.path_info}\" " \
33
- "#{status_code} #{format('%.2f', duration)}ms"
33
+ log_message = "[#{start_time}] \"#{http_method} #{req.path_info}\" " \
34
+ "#{status_code} #{format('%.2f', duration)}ms"
34
35
 
35
- ::Kernel.puts(log_message)
36
- end
36
+ ::Kernel.puts(log_message)
37
+ end
37
38
 
38
- private
39
+ private
39
40
 
40
- # This method is used to colorize the request method.
41
- #
42
- # @param request_method [String] The request method
43
- # @return [String] The colorized request method
44
- #
45
- # @api private
46
- #
47
- # @example:
48
- # colorize_http_method('GET') # => 'GET'.green
49
- def colorize_http_method(request_method)
50
- case request_method
51
- in 'GET' then 'GET'.green
52
- in 'POST' then 'POST'.magenta
53
- in 'PUT' then 'PUT'.yellow
54
- in 'DELETE' then 'DELETE'.red
55
- else request_method.blue
56
- end
57
- end
41
+ # This method is used to colorize the request method.
42
+ #
43
+ # @parameter request_method [String] The request method
44
+ #
45
+ # @return [String] The colorized request method
46
+ #
47
+ # @private
48
+ #
49
+ def colorize_http_method(request_method)
50
+ case request_method
51
+ in 'GET' then 'GET'.green
52
+ in 'POST' then 'POST'.magenta
53
+ in 'PUT' then 'PUT'.yellow
54
+ in 'DELETE' then 'DELETE'.red
55
+ else request_method.blue
56
+ end
57
+ end
58
58
 
59
- # This method is used to colorize the status code.
60
- #
61
- # @param status_code [String] The status code
62
- # @return [String] The colorized status code
63
- #
64
- # @api private
65
- #
66
- # @example:
67
- # colorize_status_code('200') # => '200'.green
68
- def colorize_status_code(status_code)
69
- case status_code[0]
70
- in '2' then status_code.green
71
- in '3' then status_code.blue
72
- in '4' then status_code.yellow
73
- in '5' then status_code.red
74
- else status_code
75
- end
76
- end
59
+ # This method is used to colorize the status code.
60
+ #
61
+ # @param status_code [String] The status code
62
+ #
63
+ # @return [String] The colorized status code
64
+ #
65
+ # @private
66
+ #
67
+ def colorize_status_code(status_code)
68
+ case status_code
69
+ in '2' then status_code.green
70
+ in '3' then status_code.blue
71
+ in '4' then status_code.yellow
72
+ in '5' then status_code.red
73
+ else status_code
74
+ end
75
+ end
77
76
 
78
- # This method is used to calculate the duration.
79
- #
80
- # @param start_time [Time] The start time
81
- # @param end_time [Time] The end time
82
- # @return [Float] The duration
83
- #
84
- # @api private
85
- #
86
- # @example:
87
- # calculate_duration(Time.now, Time.now + 1) # => 1000
88
- def calculate_duration(start_time, end_time)
89
- millis_in_second = 1000.0
90
- (end_time - start_time) * millis_in_second
91
- end
92
- end
93
- end
94
- end
77
+ # This method is used to calculate the duration.
78
+ #
79
+ # @param start_time [Time] The start time
80
+ #
81
+ # @param end_time [Time] The end time
82
+ # @return [Float] The duration
83
+ #
84
+ # @api private
85
+ #
86
+ def calculate_duration(start_time, end_time)
87
+ millis_in_second = 1000.0
88
+ (end_time - start_time) * millis_in_second
89
+ end
90
+ end
91
+ end
92
+ end
95
93
  end
@@ -0,0 +1,97 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Released under the MIT License.
4
+ # Copyright, 2023, by Aristóteles Coutinho.
5
+
6
+ require 'console'
7
+
8
+ # This middleware is used to reload files in development mode.
9
+ #
10
+ module Lenna
11
+ module Middleware
12
+ module Default
13
+ class Reload
14
+ attr_accessor :directories, :files_mtime
15
+
16
+ # Initializes a new instance of Middleware::Default::Reload.
17
+ #
18
+ # @parameter directories [Array] An array of directories to monitor.
19
+ #
20
+ # @return [Middleware::Default::Reload] A new instance of Middleware::Default::Reload.
21
+ def initialize(directories = [])
22
+ self.files_mtime = {}
23
+ self.directories = directories
24
+
25
+ monitor_directories(directories)
26
+ end
27
+
28
+ # Calls the middleware.
29
+ #
30
+ # @parameter req [Rack::Request] The request.
31
+ # @parameter _res [Rack::Response] The response.
32
+ # @parameter next_middleware [Proc] The next middleware.
33
+ #
34
+ # @return [void]
35
+ #
36
+ def call(_req, _res, next_middleware)
37
+ reload_if_needed
38
+
39
+ next_middleware.call
40
+ rescue ::StandardError => error
41
+ ::Console.error(self, error)
42
+ end
43
+
44
+ private
45
+
46
+ # Reloads files if needed.
47
+ #
48
+ # @return [void]
49
+ #
50
+ def reload_if_needed
51
+ modified_files = check_for_modified_files
52
+
53
+ reload_files(modified_files) unless modified_files.empty?
54
+ end
55
+
56
+ # Monitors directories for changes.
57
+ #
58
+ # @parameter directories [Array] An array of directories to monitor.
59
+ #
60
+ # @return [void]
61
+ #
62
+ def monitor_directories(directories)
63
+ directories.each do |directory|
64
+ ::Dir.glob(directory).each { |file| files_mtime[file] = ::File.mtime(file) }
65
+ end
66
+ end
67
+
68
+ # Checks for modified files.
69
+ #
70
+ # @return [Array] An array of modified files.
71
+ #
72
+ # @example
73
+ # check_for_modified_files #=> ["/path/to/file.rb"]
74
+ #
75
+ def check_for_modified_files
76
+ @files_mtime.select do |file, last_mtime|
77
+ ::File.mtime(file) > last_mtime
78
+ end.keys
79
+ end
80
+
81
+ # Reloads files.
82
+ #
83
+ # @parameter files [Array] An array of files(paths) to reload.
84
+ #
85
+ # @return [void]
86
+ #
87
+ def reload_files(files)
88
+ files.each do |file|
89
+ ::Console.debug("Reloading #{file}")
90
+ ::Kernel.load file
91
+ @files_mtime[file] = ::File.mtime(file)
92
+ end
93
+ end
94
+ end
95
+ end
96
+ end
97
+ end
@@ -1,99 +1,124 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ # Released under the MIT License.
4
+ # Copyright, 2023, by Aristóteles Coutinho.
5
+
3
6
  module Lenna
4
- class Router
5
- # The Route::Builder class is responsible for building the tree of routes.
6
- #
7
- # The tree of routes is built by adding routes to the tree. Each route is
8
- # represented by a node in the tree and each node has a path and an
9
- # endpoint. The path is the path of the route and the endpoint is then
10
- # action to be executed when the route is matched.
11
- #
12
- # Those nodes are stored in a cache to avoid rebuilding the tree of routes
13
- # for each request.
14
- #
15
- # The tree use `Trie` data structures to optimize the search for a route.
16
- # The trie is a tree where each node is a character of the path.
17
- # This way, the search for a route is O(n) where n is the length of the
18
- # path.
19
- #
20
- class Builder
21
- def initialize(root_node) = @root_node = root_node
7
+ class Router
8
+ # The Route::Builder class is responsible for building the tree of routes.
9
+ #
10
+ # The tree of routes is built by adding routes to the tree. Each route is
11
+ # represented by a node in the tree and each node has a path and an
12
+ # endpoint. The path is the path of the route and the endpoint is then
13
+ # action to be executed when the route is matched.
14
+ #
15
+ # Those nodes are stored in a cache to avoid rebuilding the tree of routes
16
+ # for each request.
17
+ #
18
+ # The tree use `Trie` data structures to optimize the search for a route.
19
+ # The trie is a tree where each node is a character of the path.
20
+ # This way, the search for a route is O(n) where n is the length of the
21
+ # path.
22
+ #
23
+ # @private
24
+ #
25
+ class Builder
26
+ def initialize(root_node) = @root_node = root_node
22
27
 
23
- # @param method [String] the HTTP method
24
- # @param path [String] the path to be matched
25
- # @param action [Proc] the action to be executed
26
- # @param cache [Cache] the cache to be used
27
- # @return [void]
28
- def call(method, path, action, cache)
29
- path_key = cache.cache_key(method, path)
28
+ # This method will add a route to the tree of routes.
29
+ #
30
+ # @parameter method [String] the HTTP method
31
+ # @parameter path [String] the path to be matched
32
+ # @parameter action [Proc] the action to be executed
33
+ # @parameter cache [Cache] the cache to be used
34
+ #
35
+ # @return [void]
36
+ #
37
+ def call(method, path, action, cache)
38
+ path_key = cache.cache_key(method, path)
30
39
 
31
- return if cache.exist?(path_key)
40
+ return if cache.exist?(path_key)
32
41
 
33
- current_node = find_or_create_route_node(path)
34
- setup_endpoint(current_node, method, action)
42
+ current_node = find_or_create_route_node(path)
43
+ setup_endpoint(current_node, method, action)
35
44
 
36
- cache.add(path_key, current_node)
37
- end
45
+ cache.add(path_key, current_node)
46
+ end
38
47
 
39
- private
48
+ private
40
49
 
41
- # This method will create routes that are missing.
42
- # @param path [String] the path to be matched
43
- # @return [Node] the node that matches the path
44
- def find_or_create_route_node(path)
45
- current_node = @root_node
46
- split_path(path).each do |part|
47
- current_node = find_or_create_node(current_node, part)
48
- end
49
- current_node
50
- end
50
+ # This method will create routes that are missing.
51
+ # @parameter path [String] the path to be matched
52
+ #
53
+ # @return [Node] the node that matches the path
54
+ #
55
+ def find_or_create_route_node(path)
56
+ current_node = @root_node
57
+ split_path(path).each do |part|
58
+ current_node = find_or_create_node(current_node, part)
59
+ end
60
+ current_node
61
+ end
51
62
 
52
- # @param current_node [Node] the current node
53
- # @param part [String] the part of the path
54
- # @return [Node] the node that matches the part of the path
55
- # @note This method will create the nodes that are missing.
56
- # This way, the tree of routes is built.
57
- # @example Given the part ':id' and the tree bellow:
58
- # root
59
- # └── users
60
- # └── :id
61
- # The method will return the node :id.
62
- # If the node :id does not exist, it will be created.
63
- # The tree will be:
64
- # root
65
- # └── users
66
- # └── :id
67
- def find_or_create_node(current_node, part)
68
- if part.start_with?(':')
69
- # If it is a placeholder, then we just create or update
70
- # the placeholder node with the placeholder name.
71
- placeholder_name = part[1..].to_sym
72
- current_node.children[:placeholder] ||= Node.new(
73
- {},
74
- nil,
75
- placeholder_name
76
- )
77
- else
78
- current_node.children[part] ||= Node.new
79
- end
80
- current_node.children[part.start_with?(':') ? :placeholder : part]
81
- end
63
+ # This method will create the nodes that are missing.
64
+ #
65
+ # @parameter current_node [Node] the current node
66
+ # @parameter part [String] the part of the path
67
+ #
68
+ # @return [Node] the node that matches the part of the path
69
+ #
70
+ # This way, the tree of routes is built.
71
+ # @example Given the part ':id' and the tree bellow:
72
+ # root
73
+ # └── users
74
+ # └── :id
75
+ # The method will return the node :id.
76
+ # If the node :id does not exist, it will be created.
77
+ # The tree will be:
78
+ # root
79
+ # └── users
80
+ # └── :id
81
+ #
82
+ def find_or_create_node(current_node, part)
83
+ if part.start_with?(':')
84
+ # If it is a placeholder, then we just create or update
85
+ # the placeholder node with the placeholder name.
86
+ placeholder_name = part[1..].to_sym
87
+ current_node.children[:placeholder] ||= Node.new(
88
+ {},
89
+ nil,
90
+ placeholder_name
91
+ )
92
+ else
93
+ current_node.children[part] ||= Node.new
94
+ end
95
+ current_node.children[part.start_with?(':') ? :placeholder : part]
96
+ end
82
97
 
83
- # @param current_node [Node] the current node
84
- # @param method [String] the HTTP method
85
- # @param action [Proc] the action to be executed
86
- def setup_endpoint(current_node, method, action)
87
- current_node.endpoint ||= {}
88
- current_node.endpoint[method] = action
89
- end
98
+ # This method will setup the endpoint of the current node.
99
+ #
100
+ # @parameter current_node [Node] the current node
101
+ # @parameter method [String] the HTTP method
102
+ # @parameter action [Proc] the action to be executed
103
+ #
104
+ # @return [void]
105
+ #
106
+ def setup_endpoint(current_node, method, action)
107
+ current_node.endpoint ||= {}
108
+ current_node.endpoint[method] = action
109
+ end
90
110
 
91
- # @param path [String] the path to be split
92
- # @return [Array] the splitted path
93
- # @todo: Move this to a separate file and require it here.
94
- # Maybe utils or something like that.
95
- # Use Rack::Utils.split_path_info instead.
96
- def split_path(path) = path.split('/').reject(&:empty?)
97
- end
98
- end
111
+ # This method will split the path into parts.
112
+ #
113
+ # @parameter path [String] the path to be split
114
+ #
115
+ # @return [Array] the splitted path
116
+ #
117
+ # TODO: Move this to a separate file and require it here.
118
+ # Maybe utils or something like that.
119
+ # Use Rack::Utils.split_path_info instead.
120
+ #
121
+ def split_path(path) = path.split('/').reject(&:empty?)
122
+ end
123
+ end
99
124
  end
@@ -1,38 +1,52 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ # Released under the MIT License.
4
+ # Copyright, 2023, by Aristóteles Coutinho.
5
+
3
6
  module Lenna
4
- class Router
5
- # @api public
6
- # @note This class is used to cache the routes.
7
- class Cache
8
- def initialize = @cache = {}
7
+ class Router
8
+ # This class is used to cache the routes.
9
+ #
10
+ # @private
11
+ #
12
+ class Cache
13
+ def initialize = @cache = {}
9
14
 
10
- # @api public
11
- # @param [String] method
12
- # @param [String] path
13
- # @return [String]
14
- # @note This method is used to generate a key for the cache.
15
- def cache_key(method, path) = "#{method} #{path}"
15
+ # This method is used to generate a key for the cache.
16
+ #
17
+ # @parameter [String] method
18
+ # @parameter [String] path
19
+ #
20
+ # @return [String]
21
+ #
22
+ def cache_key(method, path) = "#{method} #{path}"
16
23
 
17
- # @api public
18
- # @param route_key [String] The key for the route.
19
- # @param node [Lenna::Route::Node] The node for the route.
20
- # @return [Lenna::Route::Node]
21
- # @note This method is used to add a route to the cache.
22
- def add(route_key, node) = @cache[route_key] = node
24
+ # This method is used to add a route to the cache.
25
+ #
26
+ # @parameter route_key [String] The key for the route.
27
+ # @parameter node [Lenna::Route::Node] The node for the route.
28
+ #
29
+ # @return [Lenna::Route::Node]
30
+ #
31
+ def add(route_key, node) = @cache[route_key] = node
23
32
 
24
- # @api public
25
- # @param route_key [String] The key for the route.
26
- # @return [Lenna::Route::Node]
27
- # @note This method is used to get a route from the cache.
28
- def get(route_key) = @cache[route_key]
33
+ # This method is used to get a route from the cache.
34
+ #
35
+ # @parameter route_key [String] The key for the route.
36
+ #
37
+ # @return [Lenna::Route::Node]
38
+ #
39
+ def get(route_key) = @cache[route_key]
29
40
 
30
- # @api public
31
- # @param route_key [String] The key for the route.
32
- # @return [Boolean]
33
- # @note This method is used to check if a route exists
34
- # in the cache.
35
- def exist?(route_key) = @cache.key?(route_key)
36
- end
37
- end
41
+ # This method is used to check if a route is in the cache.
42
+ #
43
+ # @api public
44
+ #
45
+ # @parameter route_key [String] The key for the route.
46
+ #
47
+ # @return [Boolean]
48
+ #
49
+ def exist?(route_key) = @cache.key?(route_key)
50
+ end
51
+ end
38
52
  end