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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 729bdd148836e2e4876915282f45c5dfb13e5ac040a45ab973a40824d4a02e09
4
- data.tar.gz: b4ce94253c0bb8d5a8e7026e2c8dc5f873d748ee572756a98b240ff02e3a1f64
3
+ metadata.gz: 79d2b3d1395db396acd1aad4055141b75c955ca0ff0462e5fcf700db819af32e
4
+ data.tar.gz: 1cc17abdffbbc21540ada84f192cf9b1139c63c9422ef8152c2767ad6091473b
5
5
  SHA512:
6
- metadata.gz: 1298a4d2d07e5ff4d0a09dbc446fa2f32858a043f9ea68bcd76c0be4543aceaa0292e4f469bde1048ce63d4932eee26da0eae88a715f9dffd5a7328832ec8fbf
7
- data.tar.gz: e09f3194ff8c72a2c0dbab9f00f73ea7916deaca101427c617b6c5d04a307c50a95563805a2094a895288a7ae6874753f5cf45d61039b4435e216cdaabf5a9c5
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:
@@ -17,15 +17,7 @@ module Sandals
17
17
  end
18
18
 
19
19
  def location
20
- locations = original_error.backtrace_locations || []
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 Interrupt
24
- 0
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 = Sandals.load(path)
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
- ANSI_ESCAPE = /\e\[[0-9;]*m/
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
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Sandals
4
- VERSION = "0.1.1"
4
+ VERSION = "0.1.2"
5
5
  end
data/lib/sandals.rb CHANGED
@@ -1,6 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require_relative "sandals/version"
4
+ require_relative "sandals/error_details"
4
5
  require_relative "sandals/binding_resolver"
5
6
  require_relative "sandals/action_error"
6
7
  require_relative "sandals/uploaded_file"
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.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