lennarb 0.1.5 → 0.1.7
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/changelog.md +210 -0
- data/exe/lenna +17 -0
- data/lib/lenna/application.rb +53 -0
- data/lib/lenna/cli/app.rb +39 -0
- data/lib/lenna/cli/commands/create_project.rb +125 -0
- data/lib/lenna/cli/commands/interface.rb +20 -0
- data/lib/lenna/cli/commands/start_server.rb +143 -0
- data/lib/lenna/cli/templates/application.erb +11 -0
- data/lib/lenna/cli/templates/config.ru.erb +5 -0
- data/lib/lenna/cli/templates/gemfile.erb +14 -0
- data/lib/lenna/middleware/app.rb +107 -92
- data/lib/lenna/middleware/default/error_handler.rb +184 -179
- data/lib/lenna/middleware/default/logging.rb +79 -81
- data/lib/lenna/middleware/default/reload.rb +97 -0
- data/lib/lenna/router/builder.rb +111 -86
- data/lib/lenna/router/cache.rb +44 -30
- data/lib/lenna/router/namespace_stack.rb +66 -62
- data/lib/lenna/router/request.rb +125 -101
- data/lib/lenna/router/response.rb +505 -375
- data/lib/lenna/router/route_matcher.rb +56 -57
- data/lib/lenna/router.rb +187 -154
- data/lib/lennarb/array_extensions.rb +25 -11
- data/lib/lennarb/version.rb +5 -2
- data/lib/lennarb.rb +39 -1
- data/license.md +21 -0
- data/readme.md +31 -0
- metadata +99 -41
- data/CHANGELOG.md +0 -62
- data/LICENCE +0 -24
- data/README.md +0 -229
- data/lib/lenna/base.rb +0 -52
data/lib/lenna/middleware/app.rb
CHANGED
@@ -1,103 +1,118 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
# Released under the MIT License.
|
4
|
+
# Copyright, 2023, by Aristóteles Coutinho.
|
5
|
+
|
6
|
+
require 'singleton'
|
7
|
+
|
3
8
|
module Lenna
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
9
|
+
module Middleware
|
10
|
+
# The MiddlewareManager class is responsible for managing the middlewares.
|
11
|
+
#
|
12
|
+
# @attr global_middlewares [Array] the global middlewares
|
13
|
+
# @attr middleware_chains_cache [Hash] the middleware chains cache
|
14
|
+
#
|
15
|
+
# Middleware chains are cached by action.
|
16
|
+
# The middlewares that are added to a specific route are added to the
|
17
|
+
# global middlewares.
|
18
|
+
#
|
19
|
+
# @private Since `v0.1.0`
|
20
|
+
#
|
21
|
+
class App
|
22
|
+
include Singleton
|
23
|
+
|
24
|
+
# @return [Array] the global middlewares.
|
25
|
+
#
|
26
|
+
attr_accessor :global_middlewares
|
27
|
+
# @return [Hash] the middleware chains cache.
|
28
|
+
#
|
29
|
+
attr_accessor :middleware_chains_cache
|
23
30
|
|
24
|
-
|
25
|
-
|
31
|
+
# This method will initialize the global middlewares and the
|
32
|
+
# middleware chains cache.
|
33
|
+
#
|
34
|
+
# @return [void]
|
35
|
+
#
|
36
|
+
def initialize
|
37
|
+
@global_middlewares = []
|
38
|
+
@middleware_chains_cache = {}
|
39
|
+
end
|
26
40
|
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
@middleware_chains_cache = {}
|
37
|
-
end
|
41
|
+
# This method is used to reset the global middlewares and the middleware
|
42
|
+
# chains cache.
|
43
|
+
#
|
44
|
+
# @return [void]
|
45
|
+
#
|
46
|
+
def reset!
|
47
|
+
@global_middlewares = []
|
48
|
+
@middleware_chains_cache = {}
|
49
|
+
end
|
38
50
|
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
@middleware_chains_cache = {}
|
48
|
-
end
|
49
|
-
end
|
51
|
+
# This method is used to add a middleware to the global middlewares.
|
52
|
+
# @parameter middlewares [Array] the middlewares to be used
|
53
|
+
# @return [void]
|
54
|
+
#
|
55
|
+
def use(middlewares)
|
56
|
+
@global_middlewares += Array(middlewares)
|
57
|
+
@middleware_chains_cache = {}
|
58
|
+
end
|
50
59
|
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
60
|
+
# This method is used to fetch or build the middleware chain for the given
|
61
|
+
# action and route middlewares.
|
62
|
+
#
|
63
|
+
# @parameter action [Proc] the action to be executed
|
64
|
+
# @parameter route_middlewares [Array] the middlewares to be used
|
65
|
+
# @return [Proc] the middleware chain
|
66
|
+
#
|
67
|
+
#
|
68
|
+
def fetch_or_build_middleware_chain(
|
69
|
+
action,
|
70
|
+
route_middlewares,
|
71
|
+
http_method: nil,
|
72
|
+
path: nil
|
73
|
+
)
|
74
|
+
signature =
|
75
|
+
if http_method && path
|
76
|
+
[http_method, path, route_middlewares].hash.to_s
|
77
|
+
else
|
78
|
+
['global', route_middlewares].hash.to_s
|
79
|
+
end
|
63
80
|
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
end
|
68
|
-
end
|
81
|
+
@middleware_chains_cache[signature] ||=
|
82
|
+
build_middleware_chain(action, route_middlewares)
|
83
|
+
end
|
69
84
|
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
85
|
+
# This method is used to build the middleware chain for the given action
|
86
|
+
# and middlewares.
|
87
|
+
#
|
88
|
+
# @parameter action [Proc] the action to be executed
|
89
|
+
# @parameter middlewares [Array] the middlewares to be used
|
90
|
+
# @return [Proc] the middleware chain
|
91
|
+
#
|
92
|
+
# ex.
|
93
|
+
# Given the action:
|
94
|
+
# `->(req, res) { res << 'Hello' }` and the
|
95
|
+
# middlewares [mw1, mw2], the middleware
|
96
|
+
# chain will be:
|
97
|
+
# mw1 -> mw2 -> action
|
98
|
+
# The action will be the last middleware in the
|
99
|
+
# chain.
|
100
|
+
#
|
101
|
+
def build_middleware_chain(action, middlewares)
|
102
|
+
all_middlewares = (@global_middlewares + Array(middlewares))
|
88
103
|
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
104
|
+
all_middlewares.reverse.reduce(action) do |next_middleware, middleware|
|
105
|
+
->(req, res) {
|
106
|
+
middleware.call(
|
107
|
+
req,
|
108
|
+
res,
|
109
|
+
-> {
|
110
|
+
next_middleware.call(req, res)
|
111
|
+
}
|
112
|
+
)
|
113
|
+
}
|
114
|
+
end
|
115
|
+
end
|
116
|
+
end
|
117
|
+
end
|
103
118
|
end
|
@@ -1,97 +1,103 @@
|
|
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 'cgi'
|
4
7
|
|
5
8
|
module Lenna
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
9
|
+
module Middleware
|
10
|
+
module Default
|
11
|
+
# This middleware will handle errors.
|
12
|
+
#
|
13
|
+
# @private Since `v0.1.0`
|
14
|
+
#
|
15
|
+
module ErrorHandler
|
16
|
+
extend self
|
17
|
+
|
18
|
+
# This method will be called by the server.
|
19
|
+
#
|
20
|
+
# @parameter req [Rack::Request] The request object
|
21
|
+
# @parameter res [Rack::Response] The response object
|
22
|
+
# @parameter next_middleware [Proc] The next middleware in the stack
|
23
|
+
#
|
24
|
+
# @return [void]
|
25
|
+
#
|
26
|
+
def call(req, res, next_middleware)
|
27
|
+
next_middleware.call
|
28
|
+
rescue StandardError => e
|
29
|
+
env = req.env
|
30
|
+
log_error(env, e)
|
31
|
+
|
32
|
+
render_error_page(e, req, res)
|
33
|
+
end
|
34
|
+
|
35
|
+
private
|
36
|
+
|
37
|
+
# This method will render the error page.
|
38
|
+
#
|
39
|
+
# @parameter error [StandardError] The error object
|
40
|
+
# @parameter env [Hash] The environment variables
|
41
|
+
# @parameter res [Rack::Response] The response object
|
42
|
+
#
|
43
|
+
# @return [void]
|
44
|
+
#
|
45
|
+
def render_error_page(error, req, res)
|
46
|
+
case req.headers['Content-Type']
|
47
|
+
in 'application/json' then render_json(error, res)
|
48
|
+
else render_html(error, res)
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
# This method will render the JSON error page.
|
53
|
+
#
|
54
|
+
# @parameter error [StandardError] The error object
|
55
|
+
# @parameter env [Hash] The environment variables
|
56
|
+
# @parameter res [Rack::Response] The response object
|
57
|
+
#
|
58
|
+
# @return [void]
|
59
|
+
#
|
60
|
+
def render_json(error, res)
|
61
|
+
case ENV.fetch('RACK_ENV', 'development')
|
62
|
+
in 'development' | 'test' then res.json(
|
63
|
+
data: {
|
64
|
+
error: error.message,
|
65
|
+
status: 500
|
66
|
+
}
|
67
|
+
)
|
68
|
+
else res.json(data: { error: 'Internal Server Error', status: 500 })
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
# This method will render the HTML error page.
|
73
|
+
#
|
74
|
+
# @parameter error [StandardError] The error object
|
75
|
+
# @parameter env [Hash] The environment variables
|
76
|
+
# @parameter res [Rack::Response] The response object
|
77
|
+
#
|
78
|
+
# @return [void]
|
79
|
+
#
|
80
|
+
def render_html(error, res)
|
81
|
+
res.html(error_page(error), status: 500)
|
82
|
+
end
|
83
|
+
|
84
|
+
# This method will log the error.
|
85
|
+
#
|
86
|
+
# @parameter env [Hash] The environment variables
|
87
|
+
# @parameter error [StandardError] The error object
|
88
|
+
#
|
89
|
+
# @return [void]
|
90
|
+
#
|
91
|
+
def log_error(env, error)
|
92
|
+
env['rack.errors'].puts error.message
|
93
|
+
env['rack.errors'].puts error.backtrace.join("\n")
|
94
|
+
env['rack.errors'].flush
|
95
|
+
end
|
96
|
+
|
97
|
+
# This method will render the error page.
|
98
|
+
#
|
99
|
+
def error_page(error)
|
100
|
+
style = <<-STYLE
|
95
101
|
<style>
|
96
102
|
body { font-family: 'Helvetica Neue', sans-serif; background-color: #F7F7F7; color: #333; margin: 0; padding: 0; }
|
97
103
|
.error-container { max-width: 600px; margin: 20px auto; padding: 20px; box-shadow: 0 4px 8px 0 rgba(0,0,0,0.1); background: white; border-radius: 4px; }
|
@@ -105,18 +111,18 @@ module Lenna
|
|
105
111
|
svg { fill: #e74c3c; width: 50px; height: auto; }
|
106
112
|
.container { display: flex; justify-content: space-between; align-items: center; align-content: center;}
|
107
113
|
</style>
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
114
|
+
STYLE
|
115
|
+
|
116
|
+
# SVG logo
|
117
|
+
svg_logo = <<~SVG
|
118
|
+
<svg xmlns="http://www.w3.org/2000/svg" x="0px" y="0px" width="100" height="100" viewBox="0 0 100 100">
|
119
|
+
<path fill="#f1bc19" d="M77 12A1 1 0 1 0 77 14A1 1 0 1 0 77 12Z"></path><path fill="#e4e4f9" d="M50 13A37 37 0 1 0 50 87A37 37 0 1 0 50 13Z"></path><path fill="#f1bc19" d="M83 11A4 4 0 1 0 83 19A4 4 0 1 0 83 11Z"></path><path fill="#8889b9" d="M87 22A2 2 0 1 0 87 26A2 2 0 1 0 87 22Z"></path><path fill="#fbcd59" d="M81 74A2 2 0 1 0 81 78 2 2 0 1 0 81 74zM15 59A4 4 0 1 0 15 67 4 4 0 1 0 15 59z"></path><path fill="#8889b9" d="M25 85A2 2 0 1 0 25 89A2 2 0 1 0 25 85Z"></path><path fill="#fff" d="M18.5 49A2.5 2.5 0 1 0 18.5 54 2.5 2.5 0 1 0 18.5 49zM79.5 32A1.5 1.5 0 1 0 79.5 35 1.5 1.5 0 1 0 79.5 32z"></path><g><path fill="#fdfcee" d="M50 25.599999999999998A24.3 24.3 0 1 0 50 74.2A24.3 24.3 0 1 0 50 25.599999999999998Z"></path><path fill="#472b29" d="M50,74.8c-13.8,0-25-11.2-25-25c0-13.8,11.2-25,25-25c13.8,0,25,11.2,25,25C75,63.6,63.8,74.8,50,74.8z M50,26.3c-13,0-23.5,10.6-23.5,23.5S37,73.4,50,73.4s23.5-10.6,23.5-23.5S63,26.3,50,26.3z"></path></g><g><path fill="#ea5167" d="M49.9 29.6A20.4 20.4 0 1 0 49.9 70.4A20.4 20.4 0 1 0 49.9 29.6Z"></path></g><g><path fill="#ef7d99" d="M50.2,32.9c10.6,0,19.3,8.2,20.1,18.5c0-0.5,0.1-1,0.1-1.5c0-11-9-20-20.2-20c-11.1,0-20.2,9-20.2,20 c0,0.5,0,1,0.1,1.5C30.9,41,39.5,32.9,50.2,32.9z"></path></g><g><path fill="#472b29" d="M69.4,44.6c-0.2,0-0.4-0.1-0.5-0.4c-0.1-0.3-0.2-0.6-0.3-0.9c-0.4-1.1-0.9-2.2-1.5-3.2 c-0.1-0.2-0.1-0.5,0.2-0.7c0.2-0.1,0.5-0.1,0.7,0.2c0.6,1.1,1.1,2.2,1.5,3.3c0.1,0.3,0.2,0.6,0.3,0.9c0.1,0.3-0.1,0.5-0.3,0.6 C69.5,44.6,69.5,44.6,69.4,44.6z"></path></g><g><path fill="#472b29" d="M50,70.8c-11.5,0-20.9-9.3-20.9-20.8c0-11.5,9.4-20.8,20.9-20.8c6,0,11.7,2.6,15.6,7c0.3,0.3,0.6,0.7,0.9,1 c0.2,0.2,0.1,0.5-0.1,0.7c-0.2,0.2-0.5,0.1-0.7-0.1c-0.3-0.3-0.5-0.7-0.8-1c-3.8-4.2-9.2-6.7-14.9-6.7c-11,0-19.9,8.9-19.9,19.8 c0,10.9,8.9,19.8,19.9,19.8s19.9-8.9,19.9-19.8c0-1-0.1-2-0.2-3c0-0.3,0.1-0.5,0.4-0.6c0.3,0,0.5,0.1,0.6,0.4 c0.2,1,0.2,2.1,0.2,3.1C70.9,61.4,61.5,70.8,50,70.8z"></path></g><g><path fill="#fdfcee" d="M56,57.1c-0.3,0-0.6-0.1-0.9-0.4l-5.2-5.2l-5.2,5.2c-0.2,0.2-0.5,0.4-0.9,0.4s-0.6-0.1-0.9-0.4 c-0.5-0.5-0.5-1.2,0-1.7l5.2-5.2L43,44.6c-0.5-0.5-0.5-1.2,0-1.7c0.2-0.2,0.5-0.4,0.9-0.4s0.6,0.1,0.9,0.4l5.2,5.2l5.2-5.2 c0.2-0.2,0.5-0.4,0.9-0.4c0.3,0,0.6,0.1,0.9,0.4s0.4,0.5,0.4,0.9s-0.1,0.6-0.4,0.9l-5.2,5.2l5.2,5.2c0.2,0.2,0.4,0.5,0.4,0.9 c0,0.3-0.1,0.6-0.4,0.9S56.3,57.1,56,57.1z"></path><path fill="#472b29" d="M56,43.1c0.2,0,0.4,0.1,0.5,0.2c0.3,0.3,0.3,0.7,0,1l-5.5,5.5l5.5,5.5c0.3,0.3,0.3,0.7,0,1 c-0.1,0.1-0.3,0.2-0.5,0.2s-0.4-0.1-0.5-0.2l-5.5-5.5l-5.5,5.5c-0.1,0.1-0.3,0.2-0.5,0.2s-0.4-0.1-0.5-0.2c-0.3-0.3-0.3-0.7,0-1 l5.5-5.5l-5.5-5.5c-0.3-0.3-0.3-0.7,0-1c0.1-0.1,0.3-0.2,0.5-0.2s0.4,0.1,0.5,0.2l5.5,5.5l5.5-5.5C55.6,43.1,55.8,43.1,56,43.1 M56,42.1c-0.5,0-0.9,0.2-1.2,0.5l-4.8,4.8l-4.8-4.8c-0.3-0.3-0.8-0.5-1.2-0.5s-0.9,0.2-1.2,0.5c-0.3,0.3-0.5,0.8-0.5,1.2 s0.2,0.9,0.5,1.2l4.8,4.8l-4.8,4.8c-0.3,0.3-0.5,0.8-0.5,1.2s0.2,0.9,0.5,1.2c0.3,0.3,0.8,0.5,1.2,0.5s0.9-0.2,1.2-0.5l4.8-4.8 l4.8,4.8c0.3,0.3,0.8,0.5,1.2,0.5s0.9-0.2,1.2-0.5c0.3-0.3,0.5-0.8,0.5-1.2s-0.2-0.9-0.5-1.2l-4.8-4.8l4.8-4.8 c0.3-0.3,0.5-0.8,0.5-1.2s-0.2-0.9-0.5-1.2C56.9,42.2,56.4,42.1,56,42.1L56,42.1z"></path></g>
|
120
|
+
</svg>
|
121
|
+
#{' '}
|
122
|
+
SVG
|
123
|
+
|
124
|
+
# HTML page
|
125
|
+
<<-HTML
|
120
126
|
<!DOCTYPE html>
|
121
127
|
<html lang="en">
|
122
128
|
<head>
|
@@ -135,28 +141,28 @@ module Lenna
|
|
135
141
|
<div class="item-right">#{svg_logo}</div>
|
136
142
|
</div>
|
137
143
|
#{' '}
|
138
|
-
#{error_message_and_backtrace(error
|
144
|
+
#{error_message_and_backtrace(error)}
|
139
145
|
</div>
|
140
146
|
</body>
|
141
147
|
</html>
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
|
151
|
-
|
152
|
-
|
153
|
-
|
154
|
-
|
155
|
-
|
156
|
-
|
157
|
-
|
158
|
-
|
159
|
-
|
148
|
+
HTML
|
149
|
+
end
|
150
|
+
|
151
|
+
# This method will render the error message and backtrace.
|
152
|
+
#
|
153
|
+
# @parameter error [StandardError] The error object
|
154
|
+
# @parameter env [Hash] The environment variables
|
155
|
+
#
|
156
|
+
# @return [String] The HTML string
|
157
|
+
#
|
158
|
+
def error_message_and_backtrace(error)
|
159
|
+
if ENV['RACK_ENV'] == 'development'
|
160
|
+
truncated_message =
|
161
|
+
error.message[0..500] + (error.message.length > 500 ? '...' : '')
|
162
|
+
|
163
|
+
file, line = error.backtrace.first.split(':')
|
164
|
+
line_number = Integer(line)
|
165
|
+
<<-DETAILS
|
160
166
|
<div class="error-details">
|
161
167
|
<h2>Error Details:</h2>
|
162
168
|
<p>
|
@@ -176,63 +182,62 @@ module Lenna
|
|
176
182
|
</p>
|
177
183
|
</details>
|
178
184
|
</div>
|
179
|
-
|
180
|
-
|
181
|
-
|
182
|
-
|
183
|
-
|
184
|
-
|
185
|
-
|
186
|
-
|
187
|
-
|
188
|
-
|
189
|
-
|
190
|
-
|
191
|
-
|
192
|
-
|
193
|
-
|
194
|
-
|
195
|
-
|
196
|
-
|
197
|
-
|
198
|
-
|
199
|
-
|
200
|
-
|
201
|
-
|
202
|
-
|
203
|
-
|
204
|
-
|
205
|
-
|
206
|
-
|
207
|
-
|
208
|
-
|
209
|
-
|
210
|
-
|
211
|
-
|
212
|
-
|
213
|
-
|
214
|
-
|
215
|
-
|
216
|
-
|
217
|
-
|
218
|
-
|
219
|
-
|
220
|
-
|
221
|
-
|
222
|
-
|
223
|
-
|
224
|
-
|
225
|
-
|
226
|
-
|
227
|
-
|
228
|
-
|
229
|
-
|
230
|
-
|
231
|
-
|
232
|
-
|
233
|
-
|
234
|
-
|
235
|
-
|
236
|
-
|
237
|
-
end
|
185
|
+
DETAILS
|
186
|
+
else
|
187
|
+
"<p>We're sorry, but something went wrong. We've been notified " \
|
188
|
+
'about this issue and will take a look at it shortly.</p>'
|
189
|
+
end
|
190
|
+
end
|
191
|
+
|
192
|
+
# This method will extract the source code.
|
193
|
+
#
|
194
|
+
# @parameter file [String] The file path
|
195
|
+
# @parameter line_number [Integer] The line number
|
196
|
+
#
|
197
|
+
# @return [String] The HTML string
|
198
|
+
#
|
199
|
+
# ex.
|
200
|
+
# extract_source('/path/to/file.rb', 10)
|
201
|
+
# # => "<strong style='color: red;'> 7: </strong> =>
|
202
|
+
# def foo\n<strong style='color: red;'> 8: </strong>
|
203
|
+
# puts 'bar'\n<strong style='color: red;'> 9: </strong>
|
204
|
+
# end\n<strong style='color: red;'> 10: </strong> foo\n<strong
|
205
|
+
# style='color: red;'> 11: </strong> "
|
206
|
+
#
|
207
|
+
def extract_source(file, line_number)
|
208
|
+
lines = ::File.readlines(file)
|
209
|
+
start_line = [line_number - 3, 0].max
|
210
|
+
end_line = [line_number + 3, lines.size].min
|
211
|
+
|
212
|
+
line_ranger = lines[start_line...end_line]
|
213
|
+
|
214
|
+
format_lines(line_ranger, line_number).join
|
215
|
+
end
|
216
|
+
|
217
|
+
# This method will format the lines.
|
218
|
+
#
|
219
|
+
# ex.
|
220
|
+
# format_lines(line_ranger, line_number)
|
221
|
+
# # => ["<strong style='color: red;'> 7: </strong> =>\n",
|
222
|
+
# "<strong style='color: red;'> 8: </strong> puts 'bar'\n",
|
223
|
+
# "<strong style='color: red;'> 9: </strong> end\n",
|
224
|
+
# "<strong style='color: red;'> 10: </strong> foo\n",
|
225
|
+
# "<strong style='color: red;'> 11: </strong> "]
|
226
|
+
#
|
227
|
+
def format_lines(lines, highlight_line)
|
228
|
+
lines.map.with_index(highlight_line - 3 + 1) do |line, line_num|
|
229
|
+
line_number_text = "#{line_num.to_s.rjust(6)}: "
|
230
|
+
formatted_line = ::CGI.escapeHTML(line)
|
231
|
+
|
232
|
+
if line_num == highlight_line
|
233
|
+
"<strong style='color: red;'>#{line_number_text}</strong> " \
|
234
|
+
"#{formatted_line}"
|
235
|
+
else
|
236
|
+
"#{line_number_text}#{formatted_line}"
|
237
|
+
end
|
238
|
+
end
|
239
|
+
end
|
240
|
+
end
|
241
|
+
end
|
242
|
+
end
|
238
243
|
end
|