sandals 0.1.1 → 0.1.2
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 +4 -4
- data/README.md +22 -0
- data/lib/sandals/action_error.rb +1 -9
- data/lib/sandals/cli.rb +34 -3
- data/lib/sandals/code_reloader.rb +2 -32
- data/lib/sandals/error_details.rb +67 -0
- data/lib/sandals/version.rb +1 -1
- data/lib/sandals.rb +1 -0
- metadata +2 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 79d2b3d1395db396acd1aad4055141b75c955ca0ff0462e5fcf700db819af32e
|
|
4
|
+
data.tar.gz: 1cc17abdffbbc21540ada84f192cf9b1139c63c9422ef8152c2767ad6091473b
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 6002d0c758974e5e797e153ebd50c0422b2a706b8329d6831cd44c7a13daabee0c9f9bd55aa49b3389e7798c00195ad92156f8f205bc74afb1fcab7395c0df90
|
|
7
|
+
data.tar.gz: 075fb63042fd98661f107e249ff3af9c3c82bf203d44f7a58c937a8bdf7ac71fc235d4ae22246972b26ecb8cde00a246f9968bb343a9ffb9306a4f509e52d32f
|
data/README.md
CHANGED
|
@@ -75,6 +75,28 @@ ruby hello_test.rb
|
|
|
75
75
|
The test fills in `"Your name"` and verifies that the greeting appears. It uses
|
|
76
76
|
Minitest, which is installed with Sandals.
|
|
77
77
|
|
|
78
|
+
## Startup errors
|
|
79
|
+
|
|
80
|
+
Sandals reports startup failures with the error type, application file, and
|
|
81
|
+
line number instead of printing an internal framework backtrace:
|
|
82
|
+
|
|
83
|
+
```text
|
|
84
|
+
sandals: SyntaxError at app.rb:12
|
|
85
|
+
app.rb:12: syntax error, unexpected end-of-input
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
Missing dependencies are reported in the same way:
|
|
89
|
+
|
|
90
|
+
```text
|
|
91
|
+
sandals: LoadError at app.rb:1
|
|
92
|
+
cannot load such file -- sqlite3
|
|
93
|
+
Add the missing dependency to the app and try again.
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
Sandals also evaluates the initial view before opening the server, so an
|
|
97
|
+
exception during the first render points to the relevant line in the
|
|
98
|
+
application.
|
|
99
|
+
|
|
78
100
|
## The experience
|
|
79
101
|
|
|
80
102
|
An application should normally fit in a single file:
|
data/lib/sandals/action_error.rb
CHANGED
|
@@ -17,15 +17,7 @@ module Sandals
|
|
|
17
17
|
end
|
|
18
18
|
|
|
19
19
|
def location
|
|
20
|
-
|
|
21
|
-
relevant_location = locations.find do |candidate|
|
|
22
|
-
!candidate.absolute_path.to_s.include?("/lib/sandals/")
|
|
23
|
-
end || locations.first
|
|
24
|
-
return "Location unavailable" unless relevant_location
|
|
25
|
-
|
|
26
|
-
path = relevant_location.absolute_path || relevant_location.path
|
|
27
|
-
path = path.delete_prefix("#{Dir.pwd}/")
|
|
28
|
-
"#{path}:#{relevant_location.lineno}"
|
|
20
|
+
ErrorDetails.for(original_error).fetch(:location)
|
|
29
21
|
end
|
|
30
22
|
end
|
|
31
23
|
|
data/lib/sandals/cli.rb
CHANGED
|
@@ -5,6 +5,16 @@ require_relative "../sandals"
|
|
|
5
5
|
|
|
6
6
|
module Sandals
|
|
7
7
|
class CLI
|
|
8
|
+
class InitialAppError < StandardError
|
|
9
|
+
attr_reader :original_error, :path
|
|
10
|
+
|
|
11
|
+
def initialize(original_error, path)
|
|
12
|
+
@original_error = original_error
|
|
13
|
+
@path = path
|
|
14
|
+
super(original_error.message)
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
|
|
8
18
|
def self.start(arguments)
|
|
9
19
|
new(arguments).run
|
|
10
20
|
end
|
|
@@ -20,11 +30,13 @@ module Sandals
|
|
|
20
30
|
when "new" then new_app
|
|
21
31
|
else usage
|
|
22
32
|
end
|
|
23
|
-
rescue
|
|
24
|
-
|
|
33
|
+
rescue InitialAppError => error
|
|
34
|
+
report_initial_app_error(error)
|
|
25
35
|
rescue StandardError => error
|
|
26
36
|
warn "sandals: #{error.message}"
|
|
27
37
|
1
|
|
38
|
+
rescue Interrupt
|
|
39
|
+
0
|
|
28
40
|
end
|
|
29
41
|
|
|
30
42
|
private
|
|
@@ -74,7 +86,7 @@ module Sandals
|
|
|
74
86
|
parser.parse!(@arguments)
|
|
75
87
|
|
|
76
88
|
path = resolve_app(@arguments.shift || "app")
|
|
77
|
-
application =
|
|
89
|
+
application = load_initial_application(path)
|
|
78
90
|
server = Server.new(application, port: options[:port], reload_path: path)
|
|
79
91
|
|
|
80
92
|
trap("INT") { server.stop }
|
|
@@ -85,6 +97,25 @@ module Sandals
|
|
|
85
97
|
0
|
|
86
98
|
end
|
|
87
99
|
|
|
100
|
+
def load_initial_application(path)
|
|
101
|
+
application = Sandals.load(path)
|
|
102
|
+
application.render(development_reload: true)
|
|
103
|
+
application
|
|
104
|
+
rescue SyntaxError, LoadError, StandardError => error
|
|
105
|
+
raise InitialAppError.new(error, File.expand_path(path))
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
def report_initial_app_error(error)
|
|
109
|
+
original = error.original_error
|
|
110
|
+
details = ErrorDetails.for(original, fallback_path: error.path)
|
|
111
|
+
warn "sandals: #{details.fetch(:class)} at #{details.fetch(:location)}"
|
|
112
|
+
warn details.fetch(:message)
|
|
113
|
+
if original.is_a?(LoadError)
|
|
114
|
+
warn "Add the missing dependency to the app and try again."
|
|
115
|
+
end
|
|
116
|
+
1
|
|
117
|
+
end
|
|
118
|
+
|
|
88
119
|
def resolve_app(name)
|
|
89
120
|
candidates = [name]
|
|
90
121
|
candidates << "#{name}.rb" unless name.end_with?(".rb")
|
|
@@ -2,9 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
module Sandals
|
|
4
4
|
class CodeReloader
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
Result = Data.define(:version, :application, :error, :changed)
|
|
5
|
+
Result = Struct.new(:version, :application, :error, :changed)
|
|
8
6
|
|
|
9
7
|
attr_reader :path
|
|
10
8
|
|
|
@@ -46,35 +44,7 @@ module Sandals
|
|
|
46
44
|
end
|
|
47
45
|
|
|
48
46
|
def error_details(error)
|
|
49
|
-
|
|
50
|
-
class: error.class.to_s,
|
|
51
|
-
message: clean_message(error),
|
|
52
|
-
location: error_location(error)
|
|
53
|
-
}
|
|
54
|
-
end
|
|
55
|
-
|
|
56
|
-
def error_location(error)
|
|
57
|
-
syntax_location =
|
|
58
|
-
clean_message(error).lines.first&.match(/\A(.+?):(\d+):/) if
|
|
59
|
-
error.is_a?(SyntaxError)
|
|
60
|
-
if syntax_location
|
|
61
|
-
source_path = syntax_location[1].delete_prefix("#{Dir.pwd}/")
|
|
62
|
-
return "#{source_path}:#{syntax_location[2]}"
|
|
63
|
-
end
|
|
64
|
-
|
|
65
|
-
locations = error.backtrace_locations || []
|
|
66
|
-
location = locations.find do |candidate|
|
|
67
|
-
!candidate.absolute_path.to_s.include?("/lib/sandals/")
|
|
68
|
-
end || locations.first
|
|
69
|
-
return path unless location
|
|
70
|
-
|
|
71
|
-
source_path = location.absolute_path || location.path
|
|
72
|
-
source_path = source_path.delete_prefix("#{Dir.pwd}/")
|
|
73
|
-
"#{source_path}:#{location.lineno}"
|
|
74
|
-
end
|
|
75
|
-
|
|
76
|
-
def clean_message(error)
|
|
77
|
-
error.message.gsub(ANSI_ESCAPE, "")
|
|
47
|
+
ErrorDetails.for(error, fallback_path: path)
|
|
78
48
|
end
|
|
79
49
|
end
|
|
80
50
|
end
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Sandals
|
|
4
|
+
class ErrorDetails
|
|
5
|
+
ANSI_ESCAPE = /\e\[[0-9;]*m/
|
|
6
|
+
|
|
7
|
+
def self.for(error, fallback_path: nil)
|
|
8
|
+
new(error, fallback_path:).to_h
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def initialize(error, fallback_path:)
|
|
12
|
+
@error = error
|
|
13
|
+
@fallback_path = fallback_path
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def to_h
|
|
17
|
+
{
|
|
18
|
+
class: @error.class.to_s,
|
|
19
|
+
message: clean_message,
|
|
20
|
+
location: error_location
|
|
21
|
+
}
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
private
|
|
25
|
+
|
|
26
|
+
def error_location
|
|
27
|
+
path, line = syntax_location || backtrace_location
|
|
28
|
+
return @fallback_path || "Location unavailable" unless path
|
|
29
|
+
|
|
30
|
+
relative_location(path, line)
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def syntax_location
|
|
34
|
+
return unless @error.is_a?(SyntaxError)
|
|
35
|
+
|
|
36
|
+
line = clean_message.lines.first&.[](/:(\d+):/, 1)
|
|
37
|
+
[@error.path || @fallback_path, line] if line
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def backtrace_location
|
|
41
|
+
locations = @error.backtrace_locations || []
|
|
42
|
+
location =
|
|
43
|
+
locations.find { |candidate| same_path?(candidate, @fallback_path) } ||
|
|
44
|
+
locations.find { |candidate| !sandals_path?(candidate) } ||
|
|
45
|
+
locations.first
|
|
46
|
+
|
|
47
|
+
[location&.absolute_path || location&.path, location&.lineno]
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def same_path?(location, path)
|
|
51
|
+
path && File.expand_path(location.absolute_path || location.path) == File.expand_path(path)
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
def sandals_path?(location)
|
|
55
|
+
location.absolute_path.to_s.include?("/lib/sandals/")
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
def relative_location(path, line)
|
|
59
|
+
source_path = path.delete_prefix("#{Dir.pwd}/")
|
|
60
|
+
"#{source_path}:#{line}"
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
def clean_message
|
|
64
|
+
@error.message.gsub(ANSI_ESCAPE, "").strip
|
|
65
|
+
end
|
|
66
|
+
end
|
|
67
|
+
end
|
data/lib/sandals/version.rb
CHANGED
data/lib/sandals.rb
CHANGED
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: sandals
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.1.
|
|
4
|
+
version: 0.1.2
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Benito Serna
|
|
@@ -56,6 +56,7 @@ files:
|
|
|
56
56
|
- lib/sandals/binding_resolver.rb
|
|
57
57
|
- lib/sandals/cli.rb
|
|
58
58
|
- lib/sandals/code_reloader.rb
|
|
59
|
+
- lib/sandals/error_details.rb
|
|
59
60
|
- lib/sandals/event_dispatcher.rb
|
|
60
61
|
- lib/sandals/html_renderer.rb
|
|
61
62
|
- lib/sandals/server.rb
|