syntropy 0.5 → 0.6

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: 7f125135bc3ed83c1050467241f48caf366600fead1d75db79455038e42e311a
4
- data.tar.gz: ee9bd2bbf906eea9c90481ecdf643126285f65f63fe66a3f2b4f3050893e7c28
3
+ metadata.gz: 553553edc3b0c81902bc3a77470bc6e9789fcd757066dcd5b4e790d38f7a28bf
4
+ data.tar.gz: fdb2cec5f581ba49d5156cce188f31a4811d29767c71833bfcfa37899ee0efc5
5
5
  SHA512:
6
- metadata.gz: 0bdce31c490701521d73829c02e1193fcd0da399b1e6186066ad5d6ecfa067dc143941300bf670fa4b447d5d5c177e903fa273d9d6d5bbfa2319ec0c74fd2678
7
- data.tar.gz: 7b5eddf105f8654f379bf1d7f52948b713ee342dbd97eb1896e28b618b442ccf33c716d27a7f67c21cfd2c65f610b5c9677cd5e4faa0e777b8f727552628865c
6
+ metadata.gz: 7340ecb01725dcc15a78a66fef5f87a113c2e9759a2305753241b0c1cf738f45b3fd6fea2dcf7bca55196cc3fc309db2ade3e7bb12292fc5aa3abe63362403f9
7
+ data.tar.gz: 48427e653e2c99022177e5475ef53b66c57143288f43e4997c40f1ba464728b310c5e0df343e7f03e3ed79f9fa8637e305b6a22296b19fb15068b7228cf0531f
data/CHANGELOG.md CHANGED
@@ -1,3 +1,7 @@
1
+ ## 0.6 2025-07-05
2
+
3
+ - Add support for middleware and error handlers
4
+
1
5
  ## 0.5 2025-07-05
2
6
 
3
7
  - Refactor App class to use Router
data/TODO.md CHANGED
@@ -1,44 +1,33 @@
1
- - Refactor routing code into a separate Router class.
2
- - The Router class is in charge of:
3
- - caching routes
4
- - loading modules
5
- - unloading modules on file change
6
- - calculating middleware for routes
7
- - middleware is defined in `_hook.rb` modules
8
- - interface: ->(req, next)
9
- - a special case for handling errors is `_error.rb`
10
- - interface: ->(req, err)
11
- - dispatching routes
12
- - error handling:
13
- - on uncaught error, if an `_error.rb` file exists in the same directory
14
- or up the file tree
15
- - middleware:
16
- - a closure is created from the composition of the different hooks
17
- defined, from the route's directory and up the file
18
- - error handlers and middleware closures are cached as part of the route's
19
- entry
20
- - on file change for any _hook.rb or _error.rb files, all route entries in
21
- the corresponding subtree are invalidated
22
-
23
-
24
- - Middleware
1
+ - Some standard middleware:
2
+
3
+ - request rewriter
4
+ - logger
5
+ - auth
6
+ - selector + terminator
25
7
 
26
8
  ```Ruby
27
- # site/_hook.rb
28
- export ->(req, &app) do
29
- app.call(req)
30
- rescue Syntropy::Error => e
31
- render_error_page(req, e.http_status)
9
+ # For the chainable DSL shown below, we need to create a custom class:
10
+ class Syntropy::Middleware::Selector
11
+ def initialize(select_proc, terminator_proc = nil)
12
+ @select_proc = select_proc
13
+ @terminator_proc = terminator_proc
14
+ end
15
+
16
+ def to_proc
17
+ ->(req, proc) {
18
+ @select_proc.(req) ? @terminator_proc.(req) : proc(req)
19
+ }
20
+ end
21
+
22
+ def terminate(&proc)
23
+ @terminator_proc = proc
24
+ end
32
25
  end
33
26
 
34
- # an alternative, at least for errors is a _error.rb file:
35
- # site/_error.rb
36
- # Just a normal callable:
37
- #
38
- export ->(req, err) do
39
- render_error_page(req, err.http_status)
40
- end
27
+ def Syntropy
28
+ ```
41
29
 
30
+ ```Ruby
42
31
  # a _site.rb file can be used to wrap a whole app
43
32
  # site/_site.rb
44
33
 
data/lib/syntropy/app.rb CHANGED
@@ -65,21 +65,32 @@ module Syntropy
65
65
  private
66
66
 
67
67
  def render_entry(req, entry)
68
+ kind = entry[:kind]
69
+ return respond_not_found(req) if kind == :not_found
70
+
71
+ entry[:proc] ||= calculate_route_proc(entry)
72
+ entry[:proc].(req)
73
+ end
74
+
75
+ def calculate_route_proc(entry)
76
+ render_proc = route_render_proc(entry)
77
+ @router.calc_route_proc_with_hooks(entry, render_proc)
78
+ end
79
+
80
+ def route_render_proc(entry)
68
81
  case entry[:kind]
69
- when :not_found
70
- respond_not_found(req, entry)
71
82
  when :static
72
- respond_static(req, entry)
83
+ ->(req) { respond_static(req, entry) }
73
84
  when :markdown
74
- respond_markdown(req, entry)
85
+ ->(req) { respond_markdown(req, entry) }
75
86
  when :module
76
- respond_module(req, entry)
87
+ load_module(entry)
77
88
  else
78
89
  raise 'Invalid entry kind'
79
90
  end
80
91
  end
81
92
 
82
- def respond_not_found(req, _entry)
93
+ def respond_not_found(req)
83
94
  headers = { ':status' => Qeweney::Status::NOT_FOUND }
84
95
  case req.method
85
96
  when 'head'
@@ -2,8 +2,6 @@
2
2
 
3
3
  module Syntropy
4
4
  class Router
5
- attr_reader :cache
6
-
7
5
  def initialize(opts, module_loader = nil)
8
6
  raise 'Invalid location given' if !File.directory?(opts[:location])
9
7
 
@@ -32,6 +30,10 @@ module Syntropy
32
30
  @machine.spin { file_watcher_loop }
33
31
  end
34
32
 
33
+ def calc_route_proc_with_hooks(entry, proc)
34
+ compose_up_tree_hooks(entry[:fn], proc)
35
+ end
36
+
35
37
  private
36
38
 
37
39
  HIDDEN_RE = /^_/
@@ -204,5 +206,35 @@ module Syntropy
204
206
  def remove_entry_cache_keys(entry)
205
207
  entry[:cache_keys]&.each_key { @cache.delete(it) }.clear
206
208
  end
209
+
210
+ def compose_up_tree_hooks(path, proc)
211
+ parent = File.dirname(path)
212
+ proc = hook_wrap_if_exists(File.join(parent, '_hook.rb'), proc)
213
+ proc = error_handler_wrap_if_exists(File.join(parent, '_error.rb'), proc)
214
+ return proc if parent == @root
215
+
216
+ compose_up_tree_hooks(parent, proc)
217
+ end
218
+
219
+ def hook_wrap_if_exists(hook_fn, proc)
220
+ return proc if !File.file?(hook_fn)
221
+
222
+ ref = path_rel(hook_fn).gsub(/\.rb$/, '')
223
+ hook_proc = @module_loader.load(ref)
224
+ ->(req) { hook_proc.(req, proc) }
225
+ end
226
+
227
+ def error_handler_wrap_if_exists(error_handler_fn, proc)
228
+ return proc if !File.file?(error_handler_fn)
229
+
230
+ ref = path_rel(error_handler_fn).gsub(/\.rb$/, '')
231
+ error_proc = @module_loader.load(ref)
232
+
233
+ proc do |req|
234
+ proc.(req)
235
+ rescue StandardError => e
236
+ error_proc.(req, e)
237
+ end
238
+ end
207
239
  end
208
240
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Syntropy
4
- VERSION = '0.5'
4
+ VERSION = '0.6'
5
5
  end
data/test/app/_hook.rb ADDED
@@ -0,0 +1,5 @@
1
+ export ->(req, proc) {
2
+ req.ctx[:foo] = req.query[:foo]
3
+ # p proc: proc
4
+ proc.(req)
5
+ }
@@ -0,0 +1,6 @@
1
+ DEFAULT_STATUS = Qeweney::Status::INTERNAL_SERVER_ERROR
2
+
3
+ export ->(req, err) {
4
+ status = err.respond_to?(:http_status) ? err.http_status : DEFAULT_STATUS
5
+ req.respond("<h1>#{err.message}</h1>", ':status' => status, 'Content-Type' => 'text/html')
6
+ }
@@ -0,0 +1,3 @@
1
+ export ->(req) {
2
+ raise 'Raised error'
3
+ }
data/test/test_app.rb CHANGED
@@ -140,6 +140,18 @@ class AppTest < Minitest::Test
140
140
  ensure
141
141
  IO.write(@tmp_fn, orig_body) if orig_body
142
142
  end
143
+
144
+ def test_middleware
145
+ req = make_request(':method' => 'HEAD', ':path' => '/test?foo=42')
146
+ assert_equal Status::OK, req.response_status
147
+ assert_nil req.response_body
148
+ assert_equal '42', req.ctx[:foo]
149
+
150
+ req = make_request(':method' => 'HEAD', ':path' => '/test/about/raise?foo=43')
151
+ assert_equal Status::INTERNAL_SERVER_ERROR, req.response_status
152
+ assert_equal '<h1>Raised error</h1>', req.response_body
153
+ assert_equal '43', req.ctx[:foo]
154
+ end
143
155
  end
144
156
 
145
157
  class CustomAppTest < Minitest::Test
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: syntropy
3
3
  version: !ruby/object:Gem::Version
4
- version: '0.5'
4
+ version: '0.6'
5
5
  platform: ruby
6
6
  authors:
7
7
  - Sharon Rosner
@@ -202,12 +202,15 @@ files:
202
202
  - lib/syntropy/side_run.rb
203
203
  - lib/syntropy/version.rb
204
204
  - syntropy.gemspec
205
+ - test/app/_hook.rb
205
206
  - test/app/_layout/default.rb
206
207
  - test/app/_lib/callable.rb
207
208
  - test/app/_lib/klass.rb
208
209
  - test/app/_lib/missing-export.rb
210
+ - test/app/about/_error.rb
209
211
  - test/app/about/foo.md
210
212
  - test/app/about/index.rb
213
+ - test/app/about/raise.rb
211
214
  - test/app/api+.rb
212
215
  - test/app/assets/style.css
213
216
  - test/app/bar.rb