syntropy 0.13.1 → 0.15
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 +11 -0
- data/TODO.md +27 -0
- data/bin/syntropy +18 -14
- data/lib/syntropy/app.rb +32 -26
- data/lib/syntropy/markdown.rb +3 -3
- data/lib/syntropy/module.rb +140 -14
- data/lib/syntropy/routing_tree.rb +2 -28
- data/lib/syntropy/utils.rb +4 -4
- data/lib/syntropy/version.rb +1 -1
- data/syntropy.gemspec +2 -2
- data/test/app/_lib/dep.rb +7 -0
- data/test/test_module.rb +10 -0
- data/test/test_routing_tree.rb +0 -52
- metadata +6 -5
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 8fda8af32a2963e75aa4f1a600ecda8d3d9c927377d9a972314ebb28d35575fb
|
4
|
+
data.tar.gz: 7fe707495be32c3c72aa0d6d7019056eaac16567caa5d3b5dfe8e8541e17f234
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 266dd7fdbd8b5cb6b9a68cf6cab367c1f089ac2a59141d19368614d8bb0eda7c52d1697d1d67bb404ea35894673fea02ee2ed655d16a7cf26209bd20bcaa3b63
|
7
|
+
data.tar.gz: 2e31bc7fabe3e59557dbb719d7148f9a8f77cd685806fb0e9cbbd187f8ef3aedab12a1a256b915fd461e82e450b8c6b25e8b548d50b09e1d782a95351546f335
|
data/CHANGELOG.md
CHANGED
@@ -1,3 +1,14 @@
|
|
1
|
+
# 0.15 2025-08-31
|
2
|
+
|
3
|
+
- Implement invalidation of reverse dependencies on module file change
|
4
|
+
|
5
|
+
# 0.14 2025-08-30
|
6
|
+
|
7
|
+
- Tweak "boot" sequence
|
8
|
+
- Update dependencies
|
9
|
+
- Log errors in App#call
|
10
|
+
- Improve module loading, add logging
|
11
|
+
|
1
12
|
# 0.13 2025-08-28
|
2
13
|
|
3
14
|
- Reimplement module loading
|
data/TODO.md
CHANGED
@@ -1,3 +1,30 @@
|
|
1
|
+
## Immediate
|
2
|
+
|
3
|
+
- [ ] Collection - treat directories and files as collections of data.
|
4
|
+
|
5
|
+
Kind of similar to the routing tree, but instead of routes it just takes a
|
6
|
+
bunch of files and turns it into a dataset. Each directory is a "table" and is
|
7
|
+
composed of zero or more files that form rows in the table. Supported file
|
8
|
+
formats:
|
9
|
+
|
10
|
+
- foo.md - markdown with optional front matter
|
11
|
+
- foo.json - JSON record
|
12
|
+
- foo.yml - YAML record
|
13
|
+
|
14
|
+
API:
|
15
|
+
|
16
|
+
```ruby
|
17
|
+
Articles = @app.collection('_articles/*.md')
|
18
|
+
article = Articles.last_by(&:date)
|
19
|
+
|
20
|
+
article.title #=>
|
21
|
+
article.date #=>
|
22
|
+
article.layout #=>
|
23
|
+
article.render_proc #=> (load layout, apply article)
|
24
|
+
article.render #=> (render to HTML)
|
25
|
+
...
|
26
|
+
```
|
27
|
+
|
1
28
|
- [ ] Improve serving of static files:
|
2
29
|
- [ ] support for compression
|
3
30
|
- [ ] support for caching headers
|
data/bin/syntropy
CHANGED
@@ -4,7 +4,7 @@
|
|
4
4
|
require 'syntropy'
|
5
5
|
require 'optparse'
|
6
6
|
|
7
|
-
|
7
|
+
env = {
|
8
8
|
mount_path: '/',
|
9
9
|
banner: Syntropy::BANNER,
|
10
10
|
logger: true
|
@@ -15,17 +15,17 @@ parser = OptionParser.new do |o|
|
|
15
15
|
|
16
16
|
o.on('-b', '--bind BIND', String,
|
17
17
|
'Bind address (default: http://0.0.0.0:1234). You can specify this flag multiple times to bind to multiple addresses.') do
|
18
|
-
|
19
|
-
|
18
|
+
env[:bind] ||= []
|
19
|
+
env[:bind] << it
|
20
20
|
end
|
21
21
|
|
22
22
|
o.on('-s', '--silent', 'Silent mode') do
|
23
|
-
|
24
|
-
|
23
|
+
env[:banner] = nil
|
24
|
+
env[:logger] = nil
|
25
25
|
end
|
26
26
|
|
27
27
|
o.on('-w', '--watch', 'Watch for changed files') do
|
28
|
-
|
28
|
+
env[:watch_files] = 0.1
|
29
29
|
end
|
30
30
|
|
31
31
|
o.on('-h', '--help', 'Show this help message') do
|
@@ -34,7 +34,7 @@ parser = OptionParser.new do |o|
|
|
34
34
|
end
|
35
35
|
|
36
36
|
o.on('-m', '--mount', 'Set mount path (default: /)') do
|
37
|
-
|
37
|
+
env[:mount_path] = it
|
38
38
|
end
|
39
39
|
|
40
40
|
o.on('-v', '--version', 'Show version') do
|
@@ -54,17 +54,21 @@ rescue StandardError => e
|
|
54
54
|
exit
|
55
55
|
end
|
56
56
|
|
57
|
-
|
57
|
+
env[:root_dir] = ARGV.shift || '.'
|
58
58
|
|
59
|
-
if !File.directory?(
|
60
|
-
puts "#{File.expand_path(
|
59
|
+
if !File.directory?(env[:root_dir])
|
60
|
+
puts "#{File.expand_path(env[:root_dir])} Not a directory"
|
61
61
|
exit
|
62
62
|
end
|
63
63
|
|
64
|
+
puts env[:banner] if env[:banner]
|
65
|
+
env[:banner] = false
|
66
|
+
|
64
67
|
|
65
68
|
# We set Syntropy.machine so we can reference it from anywhere
|
66
|
-
|
67
|
-
|
69
|
+
env[:machine] = Syntropy.machine = UM.new
|
70
|
+
env[:logger] = env[:logger] && TP2::Logger.new(env[:machine], **env)
|
68
71
|
|
69
|
-
|
70
|
-
|
72
|
+
env[:logger]&.info(message: "Running Syntropy version #{Syntropy::VERSION}")
|
73
|
+
app = Syntropy::App.load(env)
|
74
|
+
TP2.run(env) { app.call(it) }
|
data/lib/syntropy/app.rb
CHANGED
@@ -15,38 +15,38 @@ require 'syntropy/routing_tree'
|
|
15
15
|
module Syntropy
|
16
16
|
class App
|
17
17
|
class << self
|
18
|
-
def load(
|
19
|
-
site_file_app(
|
18
|
+
def load(env)
|
19
|
+
site_file_app(env) || default_app(env)
|
20
20
|
end
|
21
21
|
|
22
22
|
private
|
23
23
|
|
24
24
|
# for apps with a _site.rb file
|
25
|
-
def site_file_app(
|
26
|
-
fn = File.join(
|
25
|
+
def site_file_app(env)
|
26
|
+
fn = File.join(env[:root_dir], '_site.rb')
|
27
27
|
return nil if !File.file?(fn)
|
28
28
|
|
29
|
-
loader = Syntropy::ModuleLoader.new(
|
29
|
+
loader = Syntropy::ModuleLoader.new(env)
|
30
30
|
loader.load('_site')
|
31
31
|
end
|
32
32
|
|
33
33
|
# default app
|
34
|
-
def default_app(
|
35
|
-
new(**
|
34
|
+
def default_app(env)
|
35
|
+
new(**env)
|
36
36
|
end
|
37
37
|
end
|
38
38
|
|
39
|
-
attr_reader :module_loader, :routing_tree, :root_dir, :mount_path, :
|
40
|
-
|
41
|
-
def initialize(**opts)
|
42
|
-
@machine = opts[:machine]
|
43
|
-
@root_dir = opts[:root_dir]
|
44
|
-
@mount_path = opts[:mount_path]
|
45
|
-
@opts = opts
|
39
|
+
attr_reader :module_loader, :routing_tree, :root_dir, :mount_path, :env
|
46
40
|
|
47
|
-
|
41
|
+
def initialize(**env)
|
42
|
+
@machine = env[:machine]
|
43
|
+
@root_dir = File.expand_path(env[:root_dir])
|
44
|
+
@mount_path = env[:mount_path]
|
45
|
+
@env = env
|
46
|
+
|
47
|
+
@module_loader = Syntropy::ModuleLoader.new(app: self, **env)
|
48
48
|
setup_routing_tree
|
49
|
-
|
49
|
+
start
|
50
50
|
end
|
51
51
|
|
52
52
|
# Processes an incoming HTTP request. Requests are processed by first
|
@@ -70,8 +70,11 @@ module Syntropy
|
|
70
70
|
proc = route[:proc] ||= compute_route_proc(route)
|
71
71
|
proc.(req)
|
72
72
|
rescue StandardError => e
|
73
|
-
|
74
|
-
|
73
|
+
@env[:logger]&.error(
|
74
|
+
message: "Error while serving request",
|
75
|
+
method: req.method,
|
76
|
+
path: req.path
|
77
|
+
)
|
75
78
|
error_handler = get_error_handler(route)
|
76
79
|
error_handler.(req, e)
|
77
80
|
end
|
@@ -84,7 +87,7 @@ module Syntropy
|
|
84
87
|
# @return [void]
|
85
88
|
def setup_routing_tree
|
86
89
|
@routing_tree = Syntropy::RoutingTree.new(
|
87
|
-
root_dir: @root_dir, mount_path: @mount_path, **@
|
90
|
+
root_dir: @root_dir, mount_path: @mount_path, **@env
|
88
91
|
)
|
89
92
|
@router_proc = @routing_tree.router_proc
|
90
93
|
end
|
@@ -138,7 +141,7 @@ module Syntropy
|
|
138
141
|
end
|
139
142
|
|
140
143
|
def render_markdown(route)
|
141
|
-
atts, md = Syntropy.parse_markdown_file(route[:target][:fn], @
|
144
|
+
atts, md = Syntropy.parse_markdown_file(route[:target][:fn], @env)
|
142
145
|
|
143
146
|
if (layout = atts[:layout])
|
144
147
|
route[:applied_layouts] ||= {}
|
@@ -277,15 +280,17 @@ module Syntropy
|
|
277
280
|
# watcher according to app options.
|
278
281
|
#
|
279
282
|
# @return [void]
|
280
|
-
def
|
283
|
+
def start
|
281
284
|
@machine.spin do
|
282
285
|
# we do startup stuff asynchronously, in order to first let TP2 do its
|
283
286
|
# setup tasks
|
284
287
|
@machine.sleep 0.2
|
285
|
-
@
|
286
|
-
|
288
|
+
route_count = @routing_tree.static_map.size + @routing_tree.dynamic_map.size
|
289
|
+
@env[:logger]&.info(
|
290
|
+
message: "Serving from #{@root_dir} (#{route_count} routes found)"
|
287
291
|
)
|
288
|
-
|
292
|
+
|
293
|
+
file_watcher_loop if @env[:watch_files]
|
289
294
|
end
|
290
295
|
end
|
291
296
|
|
@@ -294,10 +299,11 @@ module Syntropy
|
|
294
299
|
#
|
295
300
|
# @return [void]
|
296
301
|
def file_watcher_loop
|
297
|
-
wf = @
|
302
|
+
wf = @env[:watch_files]
|
298
303
|
period = wf.is_a?(Numeric) ? wf : 0.1
|
299
304
|
Syntropy.file_watch(@machine, @root_dir, period: period) do |event, fn|
|
300
|
-
@
|
305
|
+
@env[:logger]&.info(message: "File change detected", fn: fn)
|
306
|
+
@module_loader.invalidate_fn(fn)
|
301
307
|
debounce_file_change
|
302
308
|
end
|
303
309
|
rescue Exception => e
|
data/lib/syntropy/markdown.rb
CHANGED
@@ -14,7 +14,7 @@ module Syntropy
|
|
14
14
|
#
|
15
15
|
# @param path [String] file path
|
16
16
|
# @return [Array] an tuple containing properties<Hash>, contents<String>
|
17
|
-
def self.parse_markdown_file(path,
|
17
|
+
def self.parse_markdown_file(path, env)
|
18
18
|
content = IO.read(path) || ''
|
19
19
|
atts = {}
|
20
20
|
|
@@ -30,9 +30,9 @@ module Syntropy
|
|
30
30
|
atts = atts.merge(yaml)
|
31
31
|
end
|
32
32
|
|
33
|
-
if
|
33
|
+
if env[:root_dir]
|
34
34
|
atts[:url] = path
|
35
|
-
.gsub(/#{
|
35
|
+
.gsub(/#{env[:root_dir]}/, '')
|
36
36
|
.gsub(/\.md$/, '')
|
37
37
|
end
|
38
38
|
|
data/lib/syntropy/module.rb
CHANGED
@@ -3,39 +3,118 @@
|
|
3
3
|
require 'p2'
|
4
4
|
|
5
5
|
module Syntropy
|
6
|
+
# The ModuleLoader class implemenets a module loader. It handles loading of
|
7
|
+
# modules, tracking of dependencies between modules, and invalidation of
|
8
|
+
# loaded modules (following a change to the module file).
|
9
|
+
#
|
10
|
+
# A module may implement a route endpoint, a layout template, utility methods,
|
11
|
+
# classes, or any other functionality needed by the web app.
|
12
|
+
#
|
13
|
+
# Modules are Ruby files that can import other modules as dependencies. A
|
14
|
+
# module must export a single value, which can be a class, a template, a proc,
|
15
|
+
# or any other Ruby object. A module can also export itself by calling `export
|
16
|
+
# self`.
|
17
|
+
#
|
18
|
+
# Modules are referenced relative to the web app's root directory, without the
|
19
|
+
# `.rb` extension. For example, for a site residing in `/my_site`, the
|
20
|
+
# reference `_lib/foo` will point to a module residing in
|
21
|
+
# `/my_site/_lib/foo.rb`.
|
6
22
|
class ModuleLoader
|
23
|
+
attr_reader :modules
|
24
|
+
|
25
|
+
# Instantiates a module loader
|
26
|
+
#
|
27
|
+
# @param env [Hash] environment hash
|
28
|
+
# @return [void]
|
7
29
|
def initialize(env)
|
8
30
|
@root_dir = env[:root_dir]
|
9
31
|
@env = env
|
10
|
-
@
|
32
|
+
@modules = {} # maps ref to module entry
|
11
33
|
@fn_map = {} # maps filename to ref
|
12
34
|
end
|
13
35
|
|
36
|
+
# Loads a module (if not already loaded) and returns its export value.
|
37
|
+
#
|
38
|
+
# @param ref [String] module reference
|
39
|
+
# @return [any] export value
|
14
40
|
def load(ref)
|
15
|
-
@
|
41
|
+
entry = (@modules[ref] ||= load_module(ref))
|
42
|
+
entry[:export_value]
|
16
43
|
end
|
17
44
|
|
18
|
-
|
45
|
+
# Invalidates a module by its filename, normally following a change to the
|
46
|
+
# underlying file (in order to cause reloading of the module). The module
|
47
|
+
# will be removed from the modules map, as well as modules dependending on
|
48
|
+
# it.
|
49
|
+
#
|
50
|
+
# @param fn [String] module filename
|
51
|
+
# @return [void]
|
52
|
+
def invalidate_fn(fn)
|
19
53
|
ref = @fn_map[fn]
|
20
54
|
return if !ref
|
21
55
|
|
22
|
-
|
23
|
-
@fn_map.delete(fn)
|
56
|
+
invalidate_ref(ref)
|
24
57
|
end
|
25
58
|
|
26
59
|
private
|
27
60
|
|
61
|
+
# Invalidates a module by its reference, normally following a change to the
|
62
|
+
# underlying file (in order to cause reloading of the module). The module
|
63
|
+
# will be removed from the modules map, as well as modules dependending on
|
64
|
+
# it.
|
65
|
+
#
|
66
|
+
# @param ref [String] module reference
|
67
|
+
# @return [void]
|
68
|
+
def invalidate_ref(ref)
|
69
|
+
entry = @modules.delete(ref)
|
70
|
+
return if !entry
|
71
|
+
|
72
|
+
@fn_map.delete(entry[:fn])
|
73
|
+
entry[:reverse_deps].each { invalidate_ref(it) }
|
74
|
+
end
|
75
|
+
|
76
|
+
# Registers reverse dependencies for the given module reference.
|
77
|
+
#
|
78
|
+
# @param ref [String] module reference
|
79
|
+
# @param deps [Array<String>] array of dependencies for the given module
|
80
|
+
# @return [void]
|
81
|
+
def add_dependencies(ref, deps)
|
82
|
+
deps.each do
|
83
|
+
entry = @modules[it]
|
84
|
+
next if !entry
|
85
|
+
|
86
|
+
entry[:reverse_deps] << ref
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
# Loads a module and returns a module entry. Any dependencies (using
|
91
|
+
# `import`) are loaded as well.
|
92
|
+
#
|
93
|
+
# @param ref [String] module reference
|
94
|
+
# @return [Hash] module entry
|
28
95
|
def load_module(ref)
|
29
96
|
fn = File.expand_path(File.join(@root_dir, "#{ref}.rb"))
|
30
|
-
@fn_map[fn] = ref
|
31
97
|
raise Syntropy::Error, "File not found #{fn}" if !File.file?(fn)
|
32
98
|
|
99
|
+
@fn_map[fn] = ref
|
33
100
|
code = IO.read(fn)
|
34
101
|
env = @env.merge(module_loader: self, ref: ref)
|
35
|
-
|
36
|
-
|
102
|
+
m = Syntropy::Module.load(env, code, fn)
|
103
|
+
add_dependencies(ref, m.__dependencies__)
|
104
|
+
export_value = transform_module_export_value(m.__export_value__)
|
105
|
+
|
106
|
+
{
|
107
|
+
fn: fn,
|
108
|
+
export_value: export_value,
|
109
|
+
reverse_deps: []
|
110
|
+
}
|
37
111
|
end
|
38
112
|
|
113
|
+
# Transforms the given export value. If the value is nil, an exception is
|
114
|
+
# raised.
|
115
|
+
#
|
116
|
+
# @param export_value [any] module's export value
|
117
|
+
# @return [any] transformed value
|
39
118
|
def transform_module_export_value(export_value)
|
40
119
|
case export_value
|
41
120
|
when nil
|
@@ -50,14 +129,28 @@ module Syntropy
|
|
50
129
|
end
|
51
130
|
end
|
52
131
|
|
132
|
+
# The Syntropy::Module class implements a reloadable module. A module is a
|
133
|
+
# `.rb` source file that implements a route endpoint, a template, utility
|
134
|
+
# methods or any other functionality needed by the web app.
|
53
135
|
class Module
|
54
|
-
|
136
|
+
# Loads a module, returning the module instance
|
137
|
+
def self.load(env, code, fn)
|
55
138
|
m = new(**env)
|
56
|
-
m.instance_eval(code)
|
57
|
-
|
139
|
+
m.instance_eval(code, fn)
|
140
|
+
env[:logger]&.info(message: "Loaded module at #{fn}")
|
141
|
+
m
|
142
|
+
rescue StandardError => e
|
143
|
+
env[:logger]&.error(
|
144
|
+
message: "Error while loading module #{fn}",
|
145
|
+
error: e
|
146
|
+
)
|
147
|
+
raise
|
58
148
|
end
|
59
149
|
|
60
|
-
|
150
|
+
# Initializes a module with the given environment hash.
|
151
|
+
#
|
152
|
+
# @param env [Hash] environment hash
|
153
|
+
# @return [void]
|
61
154
|
def initialize(**env)
|
62
155
|
@env = env
|
63
156
|
@machine = env[:machine]
|
@@ -66,16 +159,42 @@ module Syntropy
|
|
66
159
|
@ref = env[:ref]
|
67
160
|
singleton_class.const_set(:MODULE, self)
|
68
161
|
end
|
69
|
-
|
162
|
+
|
70
163
|
attr_reader :__export_value__
|
164
|
+
|
165
|
+
# Exports the given value. This value will be used as the module's
|
166
|
+
# entrypoint. It can be any Ruby value, but for a route module would
|
167
|
+
# normally be a proc.
|
168
|
+
#
|
169
|
+
# @param v [any] export value
|
170
|
+
# @return [void]
|
71
171
|
def export(v)
|
72
172
|
@__export_value__ = v
|
73
173
|
end
|
74
174
|
|
175
|
+
# Returns the list of module references imported by the module.
|
176
|
+
#
|
177
|
+
# @return [Array] array of module references
|
178
|
+
def __dependencies__
|
179
|
+
@__dependencies__ ||= []
|
180
|
+
end
|
181
|
+
|
182
|
+
# Imports the module corresponding to the given reference. The return value
|
183
|
+
# is the module's export value.
|
184
|
+
#
|
185
|
+
# @param ref [String] module reference
|
186
|
+
# @return [any] loaded dependency's export value
|
75
187
|
def import(ref)
|
76
|
-
@module_loader.load(ref)
|
188
|
+
@module_loader.load(ref).tap {
|
189
|
+
__dependencies__ << ref
|
190
|
+
}
|
77
191
|
end
|
78
192
|
|
193
|
+
# Creates and returns a P2 template created with the given block.
|
194
|
+
#
|
195
|
+
# @param proc [Proc, nil] template proc or nil
|
196
|
+
# @param block [Proc] template block
|
197
|
+
# @return [P2::Template] template
|
79
198
|
def template(proc = nil, &block)
|
80
199
|
proc ||= block
|
81
200
|
raise "No template block/proc given" if !proc
|
@@ -83,10 +202,17 @@ module Syntropy
|
|
83
202
|
P2::Template.new(proc)
|
84
203
|
end
|
85
204
|
|
205
|
+
# Returns a list of pages found at the given ref.
|
206
|
+
#
|
207
|
+
# @param ref [String] directory reference
|
208
|
+
# @return [Array] array of pages found in directory
|
86
209
|
def page_list(ref)
|
87
210
|
Syntropy.page_list(@env, ref)
|
88
211
|
end
|
89
212
|
|
213
|
+
# Creates and returns a Syntropy app for the given environment.
|
214
|
+
#
|
215
|
+
# @param env [Hash] environment
|
90
216
|
def app(**env)
|
91
217
|
Syntropy::App.new(**(@env.merge(env)))
|
92
218
|
end
|
@@ -66,32 +66,6 @@ module Syntropy
|
|
66
66
|
@router_proc ||= compile_router_proc
|
67
67
|
end
|
68
68
|
|
69
|
-
# Returns the first error handler found for the entry. If no error handler
|
70
|
-
# exists for the entry itself, the search continues up the tree through the
|
71
|
-
# entry's ancestors.
|
72
|
-
#
|
73
|
-
# @param entry [Hash] route entry
|
74
|
-
# @return [String, nil] filename of error handler, or nil if not found
|
75
|
-
def route_error_handler(entry)
|
76
|
-
return entry[:error] if entry[:error]
|
77
|
-
|
78
|
-
entry[:parent] && route_error_handler(entry[:parent])
|
79
|
-
end
|
80
|
-
|
81
|
-
# Returns a list of all hooks found up the tree from the given entry. Hooks
|
82
|
-
# are returned ordered from the tree root down to the given entry.
|
83
|
-
#
|
84
|
-
# @param entry [Hash] route entry
|
85
|
-
# @return [Array<String>] list of hook entries
|
86
|
-
def route_hooks(entry)
|
87
|
-
hooks = []
|
88
|
-
while entry
|
89
|
-
hooks.unshift(entry[:hook]) if entry[:hook]
|
90
|
-
entry = entry[:parent]
|
91
|
-
end
|
92
|
-
hooks
|
93
|
-
end
|
94
|
-
|
95
69
|
# Computes a "clean" URL path for the given path. Modules and markdown are
|
96
70
|
# stripped of their extensions, and index file paths are also converted to the
|
97
71
|
# containing directory path. For example, the clean URL path for `/foo/bar.rb`
|
@@ -233,7 +207,7 @@ module Syntropy
|
|
233
207
|
case
|
234
208
|
when (m = fn.match(/\/index(\+)?(\.(?:rb|md))$/))
|
235
209
|
make_index_module_route(m:, parent:, path: abs_path, fn:)
|
236
|
-
|
210
|
+
|
237
211
|
# index.html
|
238
212
|
when fn.match(/\/index\.html$/)
|
239
213
|
set_index_route_target(parent:, path: abs_path, kind: :static, fn:)
|
@@ -256,7 +230,7 @@ module Syntropy
|
|
256
230
|
# (modules or markdown) files) are applied as targets to the immediate
|
257
231
|
# containing directory. A + suffix indicates this route handles requests to
|
258
232
|
# its subtree
|
259
|
-
#
|
233
|
+
#
|
260
234
|
# @param m [MatchData] path match data
|
261
235
|
# @param parent [Hash] parent route entry
|
262
236
|
# @param path [String] route path
|
data/lib/syntropy/utils.rb
CHANGED
@@ -1,9 +1,9 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
|
-
|
2
|
+
|
3
3
|
module Syntropy
|
4
4
|
# Utilities for use in modules
|
5
5
|
module Utilities
|
6
|
-
# Returns a request handler that routes request according to
|
6
|
+
# Returns a request handler that routes request according to
|
7
7
|
def route_by_host(env, map = nil)
|
8
8
|
root = env[:root_dir]
|
9
9
|
sites = Dir[File.join(root, '*')]
|
@@ -17,7 +17,7 @@ module Syntropy
|
|
17
17
|
# copy over map refs
|
18
18
|
map&.each { |k, v| sites[k] = sites[v] }
|
19
19
|
|
20
|
-
#
|
20
|
+
#
|
21
21
|
lambda { |req|
|
22
22
|
site = sites[req.host]
|
23
23
|
site ? site.call(req) : req.respond(nil, ':status' => Status::BAD_REQUEST)
|
@@ -36,6 +36,6 @@ module Syntropy
|
|
36
36
|
|
37
37
|
def app(**env)
|
38
38
|
Syntropy::App.new(**env)
|
39
|
-
end
|
39
|
+
end
|
40
40
|
end
|
41
41
|
end
|
data/lib/syntropy/version.rb
CHANGED
data/syntropy.gemspec
CHANGED
@@ -26,8 +26,8 @@ Gem::Specification.new do |s|
|
|
26
26
|
s.add_dependency 'p2', '2.8'
|
27
27
|
s.add_dependency 'papercraft', '1.4'
|
28
28
|
s.add_dependency 'qeweney', '0.22'
|
29
|
-
s.add_dependency 'tp2', '0.
|
30
|
-
s.add_dependency 'uringmachine', '0.
|
29
|
+
s.add_dependency 'tp2', '0.15'
|
30
|
+
s.add_dependency 'uringmachine', '0.18'
|
31
31
|
|
32
32
|
s.add_dependency 'listen', '3.9.0'
|
33
33
|
s.add_dependency 'logger', '1.7.0'
|
data/test/test_module.rb
CHANGED
@@ -44,4 +44,14 @@ class ModuleTest < Minitest::Test
|
|
44
44
|
assert_equal @loader, mod.module_loader
|
45
45
|
assert_equal 42, mod.app
|
46
46
|
end
|
47
|
+
|
48
|
+
def test_dependency_invalidation
|
49
|
+
mod = @loader.load('_lib/dep')
|
50
|
+
assert_equal ['_lib/self', '_lib/dep'], @loader.modules.keys
|
51
|
+
|
52
|
+
self_fn = @loader.modules['_lib/self'][:fn]
|
53
|
+
@loader.invalidate_fn(self_fn)
|
54
|
+
|
55
|
+
assert_equal [], @loader.modules.keys
|
56
|
+
end
|
47
57
|
end
|
data/test/test_routing_tree.rb
CHANGED
@@ -288,58 +288,6 @@ class RoutingTreeTest < Minitest::Test
|
|
288
288
|
assert_equal 'foo', params['id']
|
289
289
|
end
|
290
290
|
|
291
|
-
def test_route_error_handler
|
292
|
-
e = @rt.dynamic_map['/docs/[org]']
|
293
|
-
target = @rt.route_error_handler(e)
|
294
|
-
assert_kind_of Hash, target
|
295
|
-
assert_equal :module, target[:kind]
|
296
|
-
assert_equal File.join(@rt.root_dir, '_error.rb'), target[:fn]
|
297
|
-
|
298
|
-
e = @rt.dynamic_map['/docs/api+']
|
299
|
-
target = @rt.route_error_handler(e)
|
300
|
-
assert_equal :module, target[:kind]
|
301
|
-
assert_equal File.join(@rt.root_dir, '_error.rb'), target[:fn]
|
302
|
-
|
303
|
-
e = @rt.dynamic_map['/docs/[org]/[repo]']
|
304
|
-
target = @rt.route_error_handler(e)
|
305
|
-
assert_equal :module, target[:kind]
|
306
|
-
assert_equal File.join(@rt.root_dir, '[org]/[repo]/_error.rb'), target[:fn]
|
307
|
-
end
|
308
|
-
|
309
|
-
def test_route_hooks
|
310
|
-
e = @rt.dynamic_map['/docs/[org]']
|
311
|
-
hooks = @rt.route_hooks(e)
|
312
|
-
assert_equal [
|
313
|
-
{ kind: :module, fn: File.join(@rt.root_dir, '_hook.rb') }
|
314
|
-
], hooks
|
315
|
-
|
316
|
-
e = @rt.dynamic_map['/docs/api+']
|
317
|
-
hooks = @rt.route_hooks(e)
|
318
|
-
assert_equal [
|
319
|
-
{ kind: :module, fn: File.join(@rt.root_dir, '_hook.rb') }
|
320
|
-
], hooks
|
321
|
-
|
322
|
-
e = @rt.dynamic_map['/docs/[org]/[repo]']
|
323
|
-
hooks = @rt.route_hooks(e)
|
324
|
-
assert_equal [
|
325
|
-
{ kind: :module, fn: File.join(@rt.root_dir, '_hook.rb') }
|
326
|
-
], hooks
|
327
|
-
|
328
|
-
e = @rt.dynamic_map['/docs/[org]/[repo]/issues']
|
329
|
-
hooks = @rt.route_hooks(e)
|
330
|
-
assert_equal [
|
331
|
-
{ kind: :module, fn: File.join(@rt.root_dir, '_hook.rb') },
|
332
|
-
{ kind: :module, fn: File.join(@rt.root_dir, '[org]/[repo]/issues/_hook.rb') }
|
333
|
-
], hooks
|
334
|
-
|
335
|
-
e = @rt.dynamic_map['/docs/[org]/[repo]/issues/[id]']
|
336
|
-
hooks = @rt.route_hooks(e)
|
337
|
-
assert_equal [
|
338
|
-
{ kind: :module, fn: File.join(@rt.root_dir, '_hook.rb') },
|
339
|
-
{ kind: :module, fn: File.join(@rt.root_dir, '[org]/[repo]/issues/_hook.rb') }
|
340
|
-
], hooks
|
341
|
-
end
|
342
|
-
|
343
291
|
def test_routing_root_mounted
|
344
292
|
rt = Syntropy::RoutingTree.new(root_dir: File.join(@root_dir, 'site'), mount_path: '/')
|
345
293
|
router = rt.router_proc
|
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.15'
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Sharon Rosner
|
@@ -85,28 +85,28 @@ dependencies:
|
|
85
85
|
requirements:
|
86
86
|
- - '='
|
87
87
|
- !ruby/object:Gem::Version
|
88
|
-
version: 0.
|
88
|
+
version: '0.15'
|
89
89
|
type: :runtime
|
90
90
|
prerelease: false
|
91
91
|
version_requirements: !ruby/object:Gem::Requirement
|
92
92
|
requirements:
|
93
93
|
- - '='
|
94
94
|
- !ruby/object:Gem::Version
|
95
|
-
version: 0.
|
95
|
+
version: '0.15'
|
96
96
|
- !ruby/object:Gem::Dependency
|
97
97
|
name: uringmachine
|
98
98
|
requirement: !ruby/object:Gem::Requirement
|
99
99
|
requirements:
|
100
100
|
- - '='
|
101
101
|
- !ruby/object:Gem::Version
|
102
|
-
version: '0.
|
102
|
+
version: '0.18'
|
103
103
|
type: :runtime
|
104
104
|
prerelease: false
|
105
105
|
version_requirements: !ruby/object:Gem::Requirement
|
106
106
|
requirements:
|
107
107
|
- - '='
|
108
108
|
- !ruby/object:Gem::Version
|
109
|
-
version: '0.
|
109
|
+
version: '0.18'
|
110
110
|
- !ruby/object:Gem::Dependency
|
111
111
|
name: listen
|
112
112
|
requirement: !ruby/object:Gem::Requirement
|
@@ -221,6 +221,7 @@ files:
|
|
221
221
|
- test/app/_hook.rb
|
222
222
|
- test/app/_layout/default.rb
|
223
223
|
- test/app/_lib/callable.rb
|
224
|
+
- test/app/_lib/dep.rb
|
224
225
|
- test/app/_lib/env.rb
|
225
226
|
- test/app/_lib/klass.rb
|
226
227
|
- test/app/_lib/missing-export.rb
|