syntropy 0.25 → 0.26
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/CHANGELOG.md +8 -0
- data/examples/index.md +1 -1
- data/lib/syntropy/app.rb +21 -10
- data/lib/syntropy/applets/builtin/default_error_handler/style.css +18 -0
- data/lib/syntropy/applets/builtin/default_error_handler.rb +55 -0
- data/lib/syntropy/applets/builtin/json_api.js +1 -1
- data/lib/syntropy/applets/builtin/req.rb +7 -0
- data/lib/syntropy/module.rb +11 -5
- data/lib/syntropy/request_extensions.rb +15 -1
- data/lib/syntropy/version.rb +1 -1
- data/syntropy.gemspec +1 -1
- data/test/app/_lib/callable.rb +1 -1
- data/test/app/_lib/dep.rb +1 -1
- data/test/app/_lib/missing-export.rb +1 -1
- data/test/app/mod/path/a.rb +3 -0
- data/test/app/mod/path/b.rb +8 -0
- data/test/test_module.rb +18 -7
- data/test/test_routing_tree.rb +1 -1
- metadata +8 -3
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 69fb0521a1229556ec52f7d64d9cb94c00a6e4000d947cd1c78b829923938906
|
|
4
|
+
data.tar.gz: 50fceb9e5cf0edd0765da05d44950e1d32fd8bd23cbbf5396df0da3cf941b880
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: b0547a29500fed8ffa15d3d81be8cce5af9a9c8b128291edf44c1eb16aab36987db2f2f16226095649609d9fb0a2b1cc18235b496d0d217fb2518e1de84dfd4b
|
|
7
|
+
data.tar.gz: 1b08a07b9220a09b199f4c411af861fe58f674a344643908e7fd691f994cfa1aadaa7782794507800d7a6834be91cc8d117b02a77d597da6bd0411f82dae4e0f
|
data/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,11 @@
|
|
|
1
|
+
# 0.26 2025-10-21
|
|
2
|
+
|
|
3
|
+
- Add /.syntropy/req route for testing request headers
|
|
4
|
+
- Add default builtin error page / response
|
|
5
|
+
- Add `Request#browser?` method for detecting browser
|
|
6
|
+
- Change behaviour of import to support relative paths
|
|
7
|
+
- Update Papercraft
|
|
8
|
+
|
|
1
9
|
# 0.25 2025-10-19
|
|
2
10
|
|
|
3
11
|
- Upgrade Papercraft to version 3.0
|
data/examples/index.md
CHANGED
data/lib/syntropy/app.rb
CHANGED
|
@@ -168,7 +168,7 @@ module Syntropy
|
|
|
168
168
|
end
|
|
169
169
|
|
|
170
170
|
# Serves a static file from the given target hash with cache validation.
|
|
171
|
-
#
|
|
171
|
+
#
|
|
172
172
|
# @param req [Qeweney::Request] request
|
|
173
173
|
# @param target [Hash] route target hash
|
|
174
174
|
# @return [void]
|
|
@@ -190,7 +190,7 @@ module Syntropy
|
|
|
190
190
|
|
|
191
191
|
# Validates and conditionally updates the file information for the given
|
|
192
192
|
# target.
|
|
193
|
-
#
|
|
193
|
+
#
|
|
194
194
|
# @param target [Hash] route target hash
|
|
195
195
|
# @return [void]
|
|
196
196
|
def validate_static_file_info(target)
|
|
@@ -203,7 +203,7 @@ module Syntropy
|
|
|
203
203
|
STATX_MASK = UM::STATX_MTIME | UM::STATX_SIZE
|
|
204
204
|
|
|
205
205
|
# Updates the static file information for the given target
|
|
206
|
-
#
|
|
206
|
+
#
|
|
207
207
|
# @param target [Hash] route target hash
|
|
208
208
|
# @param now [Time] current time
|
|
209
209
|
# @return [void]
|
|
@@ -373,12 +373,6 @@ module Syntropy
|
|
|
373
373
|
@module_loader.load(ref)
|
|
374
374
|
end
|
|
375
375
|
|
|
376
|
-
DEFAULT_ERROR_HANDLER = ->(req, err) {
|
|
377
|
-
msg = err.message
|
|
378
|
-
msg = nil if msg.empty? || (req.method == 'head')
|
|
379
|
-
req.respond(msg, ':status' => Syntropy::Error.http_status(err)) rescue nil
|
|
380
|
-
}
|
|
381
|
-
|
|
382
376
|
# Returns an error handler for the given route. If route is nil, looks up
|
|
383
377
|
# the error handler for the routing tree root. If no handler is found,
|
|
384
378
|
# returns the default error handler.
|
|
@@ -386,7 +380,7 @@ module Syntropy
|
|
|
386
380
|
# @param route [Hash] route entry
|
|
387
381
|
# @return [Proc] error handler proc
|
|
388
382
|
def get_error_handler(route)
|
|
389
|
-
route_error_handler(route || @routing_tree.root) ||
|
|
383
|
+
route_error_handler(route || @routing_tree.root) || default_error_handler
|
|
390
384
|
end
|
|
391
385
|
|
|
392
386
|
# Returns the given route's error handler, caching the result.
|
|
@@ -420,6 +414,23 @@ module Syntropy
|
|
|
420
414
|
route[:parent] && find_error_handler(route[:parent])
|
|
421
415
|
end
|
|
422
416
|
|
|
417
|
+
RAW_DEFAULT_ERROR_HANDLER = ->(req, err) {
|
|
418
|
+
msg = err.message
|
|
419
|
+
msg = nil if msg.empty? || (req.method == 'head')
|
|
420
|
+
req.respond(msg, ':status' => Syntropy::Error.http_status(err)) rescue nil
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
def default_error_handler
|
|
424
|
+
|
|
425
|
+
@default_error_handler ||= begin
|
|
426
|
+
if @builtin_applet
|
|
427
|
+
@builtin_applet.module_loader.load('/default_error_handler')
|
|
428
|
+
else
|
|
429
|
+
RAW_DEFAULT_ERROR_HANDLER
|
|
430
|
+
end
|
|
431
|
+
end
|
|
432
|
+
end
|
|
433
|
+
|
|
423
434
|
# Performs app start up, creating a log message and starting the file
|
|
424
435
|
# watcher according to app options.
|
|
425
436
|
#
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
body {
|
|
2
|
+
max-width: 800px;
|
|
3
|
+
margin: 4em auto;
|
|
4
|
+
|
|
5
|
+
font-family: Seravek, 'Gill Sans Nova', Ubuntu, Calibri, 'DejaVu Sans', source-sans-pro, sans-serif;
|
|
6
|
+
|
|
7
|
+
big {
|
|
8
|
+
display: block;
|
|
9
|
+
font-weight: bold;
|
|
10
|
+
font-size: 10em;
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
@media (max-width: 768px) {
|
|
15
|
+
body {
|
|
16
|
+
margin-inline: 1em;
|
|
17
|
+
}
|
|
18
|
+
}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
ErrorPage = ->(error:, status:, backtrace:) {
|
|
4
|
+
html {
|
|
5
|
+
head {
|
|
6
|
+
title "Syntropy error: #{error.message}"
|
|
7
|
+
meta charset: 'utf-8'
|
|
8
|
+
meta name: 'viewport', content: 'width=device-width, initial-scale=1.0'
|
|
9
|
+
link rel: 'stylesheet', type: 'text/css', href: '/.syntropy/default_error_handler/style.css'
|
|
10
|
+
}
|
|
11
|
+
body {
|
|
12
|
+
div {
|
|
13
|
+
big status
|
|
14
|
+
h2 error.message
|
|
15
|
+
if backtrace
|
|
16
|
+
p "Backtrace:"
|
|
17
|
+
ul {
|
|
18
|
+
backtrace.each {
|
|
19
|
+
li {
|
|
20
|
+
a(it[:entry], href: it[:url])
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
end
|
|
25
|
+
}
|
|
26
|
+
auto_refresh_watch!
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
def transform_backtrace(backtrace)
|
|
32
|
+
backtrace.map do
|
|
33
|
+
location = it.match(/^(.+:\d+):/)[1]
|
|
34
|
+
{ entry: it, url: "vscode://file/#{location}" }
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def error_response_browser(req, error)
|
|
39
|
+
status = Syntropy::Error.http_status(error)
|
|
40
|
+
backtrace = transform_backtrace(error.backtrace)
|
|
41
|
+
html = Papercraft.html(ErrorPage, error:, status:, backtrace:)
|
|
42
|
+
req.respond(html, ':status' => status, 'Content-Type' => 'text/html')
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def error_response_raw(req, error)
|
|
46
|
+
status = error_status_code(error)
|
|
47
|
+
msg = err.message
|
|
48
|
+
msg = nil if msg.empty? || (req.method == 'head')
|
|
49
|
+
req.respond(msg, ':status' => status)
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
export ->(req, error) {
|
|
53
|
+
req.browser? ?
|
|
54
|
+
error_response_browser(req, error) : error_response_raw(req, error)
|
|
55
|
+
}
|
data/lib/syntropy/module.rb
CHANGED
|
@@ -38,6 +38,8 @@ module Syntropy
|
|
|
38
38
|
# @param ref [String] module reference
|
|
39
39
|
# @return [any] export value
|
|
40
40
|
def load(ref)
|
|
41
|
+
ref = "/#{ref}" if ref !~ /^\//
|
|
42
|
+
|
|
41
43
|
entry = (@modules[ref] ||= load_module(ref))
|
|
42
44
|
entry[:export_value]
|
|
43
45
|
end
|
|
@@ -93,6 +95,7 @@ module Syntropy
|
|
|
93
95
|
# @param ref [String] module reference
|
|
94
96
|
# @return [Hash] module entry
|
|
95
97
|
def load_module(ref)
|
|
98
|
+
ref = "/#{ref}" if ref !~ /^\//
|
|
96
99
|
fn = File.expand_path(File.join(@root_dir, "#{ref}.rb"))
|
|
97
100
|
raise Syntropy::Error, "File not found #{fn}" if !File.file?(fn)
|
|
98
101
|
|
|
@@ -112,7 +115,7 @@ module Syntropy
|
|
|
112
115
|
|
|
113
116
|
def clean_ref(ref)
|
|
114
117
|
return '/' if ref =~ /^index(\+)?$/
|
|
115
|
-
|
|
118
|
+
|
|
116
119
|
ref.gsub(/\/index(?:\+)?$/, '')
|
|
117
120
|
end
|
|
118
121
|
|
|
@@ -205,8 +208,9 @@ module Syntropy
|
|
|
205
208
|
# @param ref [String] module reference
|
|
206
209
|
# @return [any] loaded dependency's export value
|
|
207
210
|
def import(ref)
|
|
208
|
-
|
|
209
|
-
|
|
211
|
+
path = ref =~ /^\// ? ref : File.expand_path(File.join(File.dirname(@ref), ref))
|
|
212
|
+
@module_loader.load(path).tap {
|
|
213
|
+
__dependencies__ << path
|
|
210
214
|
}
|
|
211
215
|
end
|
|
212
216
|
|
|
@@ -246,11 +250,13 @@ module Syntropy
|
|
|
246
250
|
Syntropy.page_list(@env, ref)
|
|
247
251
|
end
|
|
248
252
|
|
|
249
|
-
# Creates and returns a Syntropy app for the given environment.
|
|
253
|
+
# Creates and returns a Syntropy app for the given environment. The app's
|
|
254
|
+
# environment is based on the module's env merged with the given parameters.
|
|
250
255
|
#
|
|
251
256
|
# @param env [Hash] environment
|
|
252
257
|
def app(**env)
|
|
253
|
-
|
|
258
|
+
env = @env.merge(env)
|
|
259
|
+
Syntropy::App.new(**env)
|
|
254
260
|
end
|
|
255
261
|
end
|
|
256
262
|
end
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
require 'qeweney'
|
|
4
|
+
require 'json'
|
|
4
5
|
|
|
5
6
|
module Syntropy
|
|
6
7
|
# Extensions for the Qeweney::Request class
|
|
@@ -144,7 +145,7 @@ module Syntropy
|
|
|
144
145
|
# matches the given etag or last_modified values, responds with a 304 Not
|
|
145
146
|
# Modified status. Otherwise, yields to the given block for a normal
|
|
146
147
|
# response, and sets cache control headers according to the given arguments.
|
|
147
|
-
#
|
|
148
|
+
#
|
|
148
149
|
# @param cache_control [String] value for Cache-Control header
|
|
149
150
|
# @param etag [String, nil] Etag header value
|
|
150
151
|
# @param last_modified [String, nil] Last-Modified header value
|
|
@@ -184,6 +185,19 @@ module Syntropy
|
|
|
184
185
|
raise Syntropy::Error.new(Qeweney::Status::BAD_REQUEST, 'Invalid form data')
|
|
185
186
|
end
|
|
186
187
|
|
|
188
|
+
def html_repsonse(html)
|
|
189
|
+
respond(html, 'Content-Type' => 'text/html; charset=utf-8')
|
|
190
|
+
end
|
|
191
|
+
|
|
192
|
+
def json_response(obj)
|
|
193
|
+
respond(JSON.dump(obj), 'Content-Type' => 'application/json; charset=utf-8')
|
|
194
|
+
end
|
|
195
|
+
|
|
196
|
+
def browser?
|
|
197
|
+
user_agent = headers['user-agent']
|
|
198
|
+
user_agent && user_agent =~ /^Mozilla\//
|
|
199
|
+
end
|
|
200
|
+
|
|
187
201
|
private
|
|
188
202
|
|
|
189
203
|
BOOL_REGEXP = /^(t|f|true|false|on|off|1|0|yes|no)$/
|
data/lib/syntropy/version.rb
CHANGED
data/syntropy.gemspec
CHANGED
|
@@ -23,7 +23,7 @@ Gem::Specification.new do |s|
|
|
|
23
23
|
|
|
24
24
|
s.add_dependency 'extralite', '2.13'
|
|
25
25
|
s.add_dependency 'json', '2.13.2'
|
|
26
|
-
s.add_dependency 'papercraft', '3.0'
|
|
26
|
+
s.add_dependency 'papercraft', '3.0.1'
|
|
27
27
|
s.add_dependency 'qeweney', '0.24'
|
|
28
28
|
s.add_dependency 'tp2', '0.19'
|
|
29
29
|
s.add_dependency 'uringmachine', '0.18'
|
data/test/app/_lib/callable.rb
CHANGED
data/test/app/_lib/dep.rb
CHANGED
data/test/test_module.rb
CHANGED
|
@@ -29,6 +29,17 @@ class ModuleTest < Minitest::Test
|
|
|
29
29
|
assert_equal 43, mod.bar
|
|
30
30
|
end
|
|
31
31
|
|
|
32
|
+
def test_import_paths
|
|
33
|
+
mod = @loader.load('/mod/path/b')
|
|
34
|
+
assert_kind_of Hash, mod
|
|
35
|
+
assert_equal [:a1, :a2, :foo, :callable], mod.keys
|
|
36
|
+
|
|
37
|
+
assert_equal :foo, mod[:a1]
|
|
38
|
+
assert_equal :foo, mod[:a2]
|
|
39
|
+
assert_kind_of Syntropy::Module, mod[:foo]
|
|
40
|
+
assert_equal 'barbarbar', mod[:callable].(3)
|
|
41
|
+
end
|
|
42
|
+
|
|
32
43
|
def test_export_self
|
|
33
44
|
mod = @loader.load('_lib/self')
|
|
34
45
|
assert_kind_of Syntropy::Module, mod
|
|
@@ -39,23 +50,23 @@ class ModuleTest < Minitest::Test
|
|
|
39
50
|
mod = @loader.load('_lib/env')
|
|
40
51
|
|
|
41
52
|
assert_equal mod, mod.module_const
|
|
42
|
-
assert_equal @env.merge(module_loader: @loader, ref: '_lib/env'), mod.env
|
|
53
|
+
assert_equal @env.merge(module_loader: @loader, ref: '/_lib/env'), mod.env
|
|
43
54
|
assert_equal @machine, mod.machine
|
|
44
55
|
assert_equal @loader, mod.module_loader
|
|
45
56
|
assert_equal 42, mod.app
|
|
46
57
|
|
|
47
58
|
assert_equal mod, mod.module_const
|
|
48
|
-
assert_equal @env.merge(module_loader: @loader, ref: '_lib/env'), mod.env
|
|
59
|
+
assert_equal @env.merge(module_loader: @loader, ref: '/_lib/env'), mod.env
|
|
49
60
|
assert_equal @machine, mod.machine
|
|
50
61
|
assert_equal @loader, mod.module_loader
|
|
51
62
|
assert_equal 42, mod.app
|
|
52
63
|
end
|
|
53
64
|
|
|
54
65
|
def test_dependency_invalidation
|
|
55
|
-
|
|
56
|
-
assert_equal ['_lib/self', '_lib/dep'], @loader.modules.keys
|
|
66
|
+
_mod = @loader.load('_lib/dep')
|
|
67
|
+
assert_equal ['/_lib/self', '/_lib/dep'], @loader.modules.keys
|
|
57
68
|
|
|
58
|
-
self_fn = @loader.modules['_lib/self'][:fn]
|
|
69
|
+
self_fn = @loader.modules['/_lib/self'][:fn]
|
|
59
70
|
@loader.invalidate_fn(self_fn)
|
|
60
71
|
|
|
61
72
|
assert_equal [], @loader.modules.keys
|
|
@@ -63,9 +74,9 @@ class ModuleTest < Minitest::Test
|
|
|
63
74
|
|
|
64
75
|
def test_index_module_env
|
|
65
76
|
mod = @loader.load('mod/bar/index+')
|
|
66
|
-
assert_equal 'mod/bar', mod.env[:ref]
|
|
77
|
+
assert_equal '/mod/bar', mod.env[:ref]
|
|
67
78
|
|
|
68
79
|
mod = @loader.load('mod/foo/index')
|
|
69
|
-
assert_equal 'mod/foo', mod.env[:ref]
|
|
80
|
+
assert_equal '/mod/foo', mod.env[:ref]
|
|
70
81
|
end
|
|
71
82
|
end
|
data/test/test_routing_tree.rb
CHANGED
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.
|
|
4
|
+
version: '0.26'
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Sharon Rosner
|
|
@@ -43,14 +43,14 @@ dependencies:
|
|
|
43
43
|
requirements:
|
|
44
44
|
- - '='
|
|
45
45
|
- !ruby/object:Gem::Version
|
|
46
|
-
version:
|
|
46
|
+
version: 3.0.1
|
|
47
47
|
type: :runtime
|
|
48
48
|
prerelease: false
|
|
49
49
|
version_requirements: !ruby/object:Gem::Requirement
|
|
50
50
|
requirements:
|
|
51
51
|
- - '='
|
|
52
52
|
- !ruby/object:Gem::Version
|
|
53
|
-
version:
|
|
53
|
+
version: 3.0.1
|
|
54
54
|
- !ruby/object:Gem::Dependency
|
|
55
55
|
name: qeweney
|
|
56
56
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -203,8 +203,11 @@ files:
|
|
|
203
203
|
- lib/syntropy/applets/builtin/auto_refresh/watch.sse.rb
|
|
204
204
|
- lib/syntropy/applets/builtin/debug/debug.css
|
|
205
205
|
- lib/syntropy/applets/builtin/debug/debug.js
|
|
206
|
+
- lib/syntropy/applets/builtin/default_error_handler.rb
|
|
207
|
+
- lib/syntropy/applets/builtin/default_error_handler/style.css
|
|
206
208
|
- lib/syntropy/applets/builtin/json_api.js
|
|
207
209
|
- lib/syntropy/applets/builtin/ping.rb
|
|
210
|
+
- lib/syntropy/applets/builtin/req.rb
|
|
208
211
|
- lib/syntropy/connection_pool.rb
|
|
209
212
|
- lib/syntropy/dev_mode.rb
|
|
210
213
|
- lib/syntropy/errors.rb
|
|
@@ -238,6 +241,8 @@ files:
|
|
|
238
241
|
- test/app/index.html
|
|
239
242
|
- test/app/mod/bar/index+.rb
|
|
240
243
|
- test/app/mod/foo/index.rb
|
|
244
|
+
- test/app/mod/path/a.rb
|
|
245
|
+
- test/app/mod/path/b.rb
|
|
241
246
|
- test/app/params/[foo].rb
|
|
242
247
|
- test/app/rss.rb
|
|
243
248
|
- test/app/tmp.rb
|