syntropy 0.11 → 0.13
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 +14 -0
- data/Rakefile +1 -1
- data/TODO.md +185 -135
- data/bin/syntropy +8 -3
- data/lib/syntropy/app.rb +232 -113
- data/lib/syntropy/errors.rb +40 -12
- data/lib/syntropy/markdown.rb +4 -2
- data/lib/syntropy/module.rb +41 -79
- data/lib/syntropy/request_extensions.rb +112 -2
- data/lib/syntropy/routing_tree.rb +550 -0
- data/lib/syntropy/utils.rb +42 -0
- data/lib/syntropy/version.rb +1 -1
- data/lib/syntropy.rb +4 -1
- data/syntropy.gemspec +1 -1
- data/test/app/_lib/callable.rb +1 -1
- data/test/app/_lib/env.rb +21 -0
- data/test/app/params/[foo].rb +3 -0
- data/test/app/tmp.rb +1 -1
- data/test/app_multi_site/_site.rb +1 -1
- data/test/helper.rb +18 -2
- data/test/test_app.rb +17 -25
- data/test/test_errors.rb +38 -0
- data/test/test_module.rb +15 -5
- data/test/test_request_extensions.rb +163 -0
- data/test/test_routing_tree.rb +427 -0
- metadata +10 -6
- data/lib/syntropy/router.rb +0 -245
- data/test/test_router.rb +0 -116
- data/test/test_validation.rb +0 -35
data/lib/syntropy/app.rb
CHANGED
@@ -8,7 +8,9 @@ require 'p2'
|
|
8
8
|
|
9
9
|
require 'syntropy/errors'
|
10
10
|
require 'syntropy/file_watch'
|
11
|
+
|
11
12
|
require 'syntropy/module'
|
13
|
+
require 'syntropy/routing_tree'
|
12
14
|
|
13
15
|
module Syntropy
|
14
16
|
class App
|
@@ -19,150 +21,165 @@ module Syntropy
|
|
19
21
|
|
20
22
|
private
|
21
23
|
|
24
|
+
# for apps with a _site.rb file
|
22
25
|
def site_file_app(opts)
|
23
|
-
|
24
|
-
return nil if !File.file?(
|
26
|
+
fn = File.join(opts[:root_dir], '_site.rb')
|
27
|
+
return nil if !File.file?(fn)
|
25
28
|
|
26
|
-
loader = Syntropy::ModuleLoader.new(opts
|
29
|
+
loader = Syntropy::ModuleLoader.new(opts)
|
27
30
|
loader.load('_site')
|
28
31
|
end
|
29
32
|
|
33
|
+
# default app
|
30
34
|
def default_app(opts)
|
31
|
-
new(opts
|
35
|
+
new(**opts)
|
32
36
|
end
|
33
37
|
end
|
34
38
|
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
@
|
39
|
+
attr_reader :module_loader, :routing_tree, :root_dir, :mount_path, :opts
|
40
|
+
|
41
|
+
def initialize(**opts)
|
42
|
+
@machine = opts[:machine]
|
43
|
+
@root_dir = opts[:root_dir]
|
44
|
+
@mount_path = opts[:mount_path]
|
39
45
|
@opts = opts
|
40
46
|
|
41
|
-
@module_loader = Syntropy::ModuleLoader.new(
|
42
|
-
|
43
|
-
|
44
|
-
@machine.spin do
|
45
|
-
# we do startup stuff asynchronously, in order to first let TP2 do its
|
46
|
-
# setup tasks
|
47
|
-
@machine.sleep 0.15
|
48
|
-
@opts[:logger]&.info(
|
49
|
-
message: "Serving from #{File.expand_path(@location)}"
|
50
|
-
)
|
51
|
-
@router.start_file_watcher if opts[:watch_files]
|
52
|
-
end
|
47
|
+
@module_loader = Syntropy::ModuleLoader.new(app: self, **opts)
|
48
|
+
setup_routing_tree
|
49
|
+
start_app
|
53
50
|
end
|
54
51
|
|
52
|
+
# Processes an incoming HTTP request. Requests are processed by first
|
53
|
+
# looking up the route for the request path, then calling the route proc. If
|
54
|
+
# the route proc is not set, it is computed according to the route target,
|
55
|
+
# and composed recursively into hooks encountered up the routing tree.
|
56
|
+
#
|
57
|
+
# Normal exceptions (StandardError and descendants) are trapped and passed
|
58
|
+
# to route's error handler. If no such handler is found, the default error
|
59
|
+
# handler is used, which simply generates a textual response containing the
|
60
|
+
# error message, and with the appropriate HTTP status code, according to the
|
61
|
+
# type of error.
|
62
|
+
#
|
63
|
+
# @param req [Qeweney::Request] HTTP request
|
64
|
+
# @return [void]
|
55
65
|
def call(req)
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
66
|
+
route = @router_proc.(req.path, req.route_params)
|
67
|
+
raise Syntropy::Error.not_found('Not found') if !route
|
68
|
+
|
69
|
+
req.route = route
|
70
|
+
proc = route[:proc] ||= compute_route_proc(route)
|
71
|
+
proc.(req)
|
61
72
|
rescue StandardError => e
|
62
|
-
p e
|
63
|
-
p e.backtrace
|
64
|
-
|
73
|
+
# p e
|
74
|
+
# p e.backtrace
|
75
|
+
error_handler = get_error_handler(route)
|
76
|
+
error_handler.(req, e)
|
65
77
|
end
|
66
78
|
|
67
79
|
private
|
68
80
|
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
81
|
+
# Instantiates a routing tree with the app settings, and generates a router
|
82
|
+
# proc.
|
83
|
+
#
|
84
|
+
# @return [void]
|
85
|
+
def setup_routing_tree
|
86
|
+
@routing_tree = Syntropy::RoutingTree.new(
|
87
|
+
root_dir: @root_dir, mount_path: @mount_path, **@opts
|
88
|
+
)
|
89
|
+
@router_proc = @routing_tree.router_proc
|
75
90
|
end
|
76
91
|
|
77
|
-
|
78
|
-
|
79
|
-
|
92
|
+
# Computes the route proc for the given route, wrapping it in hooks found up
|
93
|
+
# the routing tree.
|
94
|
+
#
|
95
|
+
# @param route [Hash] route entry
|
96
|
+
# @return [Proc] route proc
|
97
|
+
def compute_route_proc(route)
|
98
|
+
pure = pure_route_proc(route)
|
99
|
+
compose_up_tree_hooks(route, pure)
|
80
100
|
end
|
81
101
|
|
82
|
-
def
|
83
|
-
case
|
102
|
+
def pure_route_proc(route)
|
103
|
+
case (kind = route[:target][:kind])
|
84
104
|
when :static
|
85
|
-
|
105
|
+
static_route_proc(route)
|
86
106
|
when :markdown
|
87
|
-
|
107
|
+
markdown_route_proc(route)
|
88
108
|
when :module
|
89
|
-
|
109
|
+
module_route_proc(route)
|
90
110
|
else
|
91
|
-
raise
|
111
|
+
raise Syntropy::Error, "Invalid route kind: #{kind.inspect}"
|
92
112
|
end
|
93
113
|
end
|
94
114
|
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
req.respond(nil, headers)
|
100
|
-
else
|
101
|
-
req.respond('Not found', headers)
|
102
|
-
end
|
103
|
-
end
|
115
|
+
# Returns a proc rendering the given static route
|
116
|
+
def static_route_proc(route)
|
117
|
+
fn = route[:target][:fn]
|
118
|
+
headers = { 'Content-Type' => Qeweney::MimeTypes[File.extname(fn)] }
|
104
119
|
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
)
|
120
|
+
->(req) {
|
121
|
+
req.respond_by_http_method(
|
122
|
+
'head' => [nil, headers],
|
123
|
+
'get' => -> { [IO.read(fn), headers] }
|
124
|
+
)
|
125
|
+
}
|
112
126
|
end
|
113
127
|
|
114
|
-
|
115
|
-
|
116
|
-
headers = { 'Content-Type' =>
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
128
|
+
# Returns a proc rendering the given markdown route
|
129
|
+
def markdown_route_proc(route)
|
130
|
+
headers = { 'Content-Type' => 'text/html' }
|
131
|
+
|
132
|
+
->(req) {
|
133
|
+
req.respond_by_http_method(
|
134
|
+
'head' => [nil, headers],
|
135
|
+
'get' => -> { [render_markdown(route), headers] }
|
136
|
+
)
|
137
|
+
}
|
121
138
|
end
|
122
139
|
|
123
|
-
def
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
140
|
+
def render_markdown(route)
|
141
|
+
atts, md = Syntropy.parse_markdown_file(route[:target][:fn], @opts)
|
142
|
+
|
143
|
+
if (layout = atts[:layout])
|
144
|
+
route[:applied_layouts] ||= {}
|
145
|
+
proc = route[:applied_layouts][layout] ||= markdown_layout_proc(layout)
|
146
|
+
html = proc.render(md: md, **atts)
|
147
|
+
else
|
148
|
+
html = P2.markdown(md)
|
128
149
|
end
|
150
|
+
html
|
151
|
+
end
|
129
152
|
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
p e.backtrace
|
136
|
-
req.respond(nil, ':status' => Qeweney::Status::INTERNAL_SERVER_ERROR)
|
153
|
+
# returns a markdown template based on the given layout
|
154
|
+
def markdown_layout_proc(layout)
|
155
|
+
@layouts ||= {}
|
156
|
+
template = @module_loader.load("_layout/#{layout}")
|
157
|
+
@layouts[layout] = template.apply { |md:, **| markdown(md) }
|
137
158
|
end
|
138
159
|
|
139
|
-
def
|
140
|
-
ref =
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
@opts[:logger]&.error(
|
145
|
-
message: "Error while loading module #{ref}",
|
146
|
-
error: e
|
147
|
-
)
|
148
|
-
:invalid
|
160
|
+
def module_route_proc(route)
|
161
|
+
ref = @routing_tree.fn_to_rel_path(route[:target][:fn])
|
162
|
+
# ref = route[:target][:fn].sub(@mount_path, '')
|
163
|
+
mod = @module_loader.load(ref)
|
164
|
+
compute_module_proc(mod)
|
149
165
|
end
|
150
166
|
|
151
|
-
def
|
167
|
+
def compute_module_proc(mod)
|
152
168
|
case mod
|
153
169
|
when P2::Template
|
154
|
-
|
170
|
+
p2_template_proc(mod)
|
155
171
|
when Papercraft::Template
|
156
|
-
|
172
|
+
papercraft_template_proc(mod)
|
157
173
|
else
|
158
174
|
mod
|
159
175
|
end
|
160
176
|
end
|
161
177
|
|
162
|
-
def
|
163
|
-
template =
|
164
|
-
|
165
|
-
|
178
|
+
def p2_template_proc(template)
|
179
|
+
template = template.proc
|
180
|
+
headers = { 'Content-Type' => 'text/html' }
|
181
|
+
|
182
|
+
->(req) {
|
166
183
|
req.respond_by_http_method(
|
167
184
|
'head' => [nil, headers],
|
168
185
|
'get' => -> { [template.render, headers] }
|
@@ -170,36 +187,138 @@ module Syntropy
|
|
170
187
|
}
|
171
188
|
end
|
172
189
|
|
173
|
-
def
|
174
|
-
lambda { |req|
|
190
|
+
def papercraft_template_proc(template)
|
175
191
|
headers = { 'Content-Type' => template.mime_type }
|
192
|
+
->(req) {
|
176
193
|
req.respond_by_http_method(
|
177
194
|
'head' => [nil, headers],
|
178
195
|
'get' => -> { [template.render, headers] }
|
179
196
|
)
|
180
197
|
}
|
181
|
-
|
182
198
|
end
|
183
199
|
|
184
|
-
|
185
|
-
|
200
|
+
# Composes the given proc into up tree hooks, recursively. Hooks have the
|
201
|
+
# signature `->(req, proc) { ... }` where proc is the pure route proc. Each
|
202
|
+
# hook therefore can decide whether to_ respond itself to the request, pass
|
203
|
+
# in additional parameters, perform any other kind of modification on the
|
204
|
+
# incoming reuqest, or capture the response from the route proc and modify
|
205
|
+
# it.
|
206
|
+
#
|
207
|
+
# Nested hooks will be invoked from the routing tree root down. For example
|
208
|
+
# `/site/_hook.rb` will wrap `/site/admin/_hook.rb` which wraps the route at
|
209
|
+
# `/site/admin/users.rb`.
|
210
|
+
#
|
211
|
+
# @param route [Hash] route entry
|
212
|
+
# @param proc [Proc] route proc
|
213
|
+
def compose_up_tree_hooks(route, proc)
|
214
|
+
hook_spec = route[:hook]
|
215
|
+
if hook_spec
|
216
|
+
orig_proc = proc
|
217
|
+
hook_proc = hook_spec[:proc] ||= load_aux_module(hook_spec)
|
218
|
+
proc = ->(req) { hook_proc.(req, orig_proc) }
|
219
|
+
end
|
186
220
|
|
187
|
-
|
188
|
-
|
189
|
-
|
190
|
-
|
191
|
-
|
192
|
-
|
221
|
+
(parent = route[:parent]) ? compose_up_tree_hooks(parent, proc) : proc
|
222
|
+
end
|
223
|
+
|
224
|
+
def load_aux_module(hook_spec)
|
225
|
+
ref = @routing_tree.fn_to_rel_path(hook_spec[:fn])
|
226
|
+
@module_loader.load(ref)
|
227
|
+
end
|
228
|
+
|
229
|
+
DEFAULT_ERROR_HANDLER = ->(req, err) {
|
230
|
+
msg = err.message
|
231
|
+
msg = nil if msg.empty? || (req.method == 'head')
|
232
|
+
req.respond(msg, ':status' => Syntropy::Error.http_status(err))
|
233
|
+
}
|
234
|
+
|
235
|
+
# Returns an error handler for the given route. If route is nil, looks up
|
236
|
+
# the error handler for the routing tree root. If no handler is found,
|
237
|
+
# returns the default error handler.
|
238
|
+
#
|
239
|
+
# @param route [Hash] route entry
|
240
|
+
# @return [Proc] error handler proc
|
241
|
+
def get_error_handler(route)
|
242
|
+
route_error_handler(route || @routing_tree.root) || DEFAULT_ERROR_HANDLER
|
243
|
+
end
|
244
|
+
|
245
|
+
# Returns the given route's error handler, caching the result.
|
246
|
+
#
|
247
|
+
# @param route [Hash] route entry
|
248
|
+
# @return [Proc] error handler proc
|
249
|
+
def route_error_handler(route)
|
250
|
+
route[:error_handler] ||= compute_error_handler(route)
|
251
|
+
end
|
252
|
+
|
253
|
+
# Finds and loads the error handler for the given route.
|
254
|
+
#
|
255
|
+
# @param route [Hash] route entry
|
256
|
+
# @return [Proc, nil] error handler proc or nil
|
257
|
+
def compute_error_handler(route)
|
258
|
+
error_target = find_error_handler(route)
|
259
|
+
return nil if !error_target
|
260
|
+
|
261
|
+
load_aux_module(error_target)
|
262
|
+
end
|
263
|
+
|
264
|
+
# Finds the closest error handler for the given route. If no error handler
|
265
|
+
# is defined for the route, searches for an error handler up the routing
|
266
|
+
# tree.
|
267
|
+
#
|
268
|
+
# @param route [Hash] route entry
|
269
|
+
# @return [Hash, nil] error handler target or nil
|
270
|
+
def find_error_handler(route)
|
271
|
+
return route[:error] if route[:error]
|
272
|
+
|
273
|
+
route[:parent] && find_error_handler(route[:parent])
|
274
|
+
end
|
275
|
+
|
276
|
+
# Performs app start up, creating a log message and starting the file
|
277
|
+
# watcher according to app options.
|
278
|
+
#
|
279
|
+
# @return [void]
|
280
|
+
def start_app
|
281
|
+
@machine.spin do
|
282
|
+
# we do startup stuff asynchronously, in order to first let TP2 do its
|
283
|
+
# setup tasks
|
284
|
+
@machine.sleep 0.2
|
285
|
+
@opts[:logger]&.info(
|
286
|
+
message: "Serving from #{File.expand_path(@location)}"
|
287
|
+
)
|
288
|
+
file_watcher_loop if opts[:watch_files]
|
193
289
|
end
|
194
|
-
html
|
195
290
|
end
|
196
291
|
|
197
|
-
|
198
|
-
|
199
|
-
|
200
|
-
|
201
|
-
|
202
|
-
|
292
|
+
# Runs the file watcher loop. When a file change is encountered, invalidates
|
293
|
+
# the corresponding module, and triggers recomputation of the routing tree.
|
294
|
+
#
|
295
|
+
# @return [void]
|
296
|
+
def file_watcher_loop
|
297
|
+
wf = @opts[:watch_files]
|
298
|
+
period = wf.is_a?(Numeric) ? wf : 0.1
|
299
|
+
Syntropy.file_watch(@machine, @root_dir, period: period) do |event, fn|
|
300
|
+
@module_loader.invalidate(fn)
|
301
|
+
debounce_file_change
|
302
|
+
end
|
303
|
+
rescue Exception => e
|
304
|
+
p e
|
305
|
+
p e.backtrace
|
306
|
+
exit!
|
307
|
+
end
|
308
|
+
|
309
|
+
# Delays responding to a file change, then reloads the routing tree.
|
310
|
+
#
|
311
|
+
# @return [void]
|
312
|
+
def debounce_file_change
|
313
|
+
if @routing_tree_reloader
|
314
|
+
@machine.schedule(@routing_tree_reloader, UM::Terminate.new)
|
315
|
+
end
|
316
|
+
|
317
|
+
@routing_tree_reloader = @machine.spin do
|
318
|
+
@machine.sleep(0.1)
|
319
|
+
setup_routing_tree
|
320
|
+
@routing_tree_reloader = nil
|
321
|
+
end
|
203
322
|
end
|
204
323
|
end
|
205
324
|
end
|
data/lib/syntropy/errors.rb
CHANGED
@@ -3,29 +3,57 @@
|
|
3
3
|
require 'qeweney'
|
4
4
|
|
5
5
|
module Syntropy
|
6
|
+
# The base Syntropy error class
|
6
7
|
class Error < StandardError
|
7
8
|
Status = Qeweney::Status
|
8
9
|
|
10
|
+
# By default, the HTTP status for errors is 500 Internal Server Error
|
11
|
+
DEFAULT_STATUS = Qeweney::Status::INTERNAL_SERVER_ERROR
|
12
|
+
|
13
|
+
# Returns the HTTP status for the given exception
|
14
|
+
#
|
15
|
+
# @param err [Exception] exception
|
16
|
+
# @return [Integer, String] HTTP status
|
17
|
+
def self.http_status(err)
|
18
|
+
err.respond_to?(:http_status) ? err.http_status : DEFAULT_STATUS
|
19
|
+
end
|
20
|
+
|
21
|
+
# Creates an error with status 404 Not Found
|
22
|
+
#
|
23
|
+
# @return [Syntropy::Error]
|
24
|
+
def self.not_found(msg = '') = new(Status::NOT_FOUND, msg)
|
25
|
+
|
26
|
+
# Creates an error with status 405 Method Not Allowed
|
27
|
+
#
|
28
|
+
# @return [Syntropy::Error]
|
29
|
+
def self.method_not_allowed(msg = '') = new(Status::METHOD_NOT_ALLOWED, msg)
|
30
|
+
|
31
|
+
# Creates an error with status 418 I'm a teapot
|
32
|
+
#
|
33
|
+
# @return [Syntropy::Error]
|
34
|
+
def self.teapot(msg = '') = new(Status::TEAPOT, msg)
|
35
|
+
|
9
36
|
attr_reader :http_status
|
10
37
|
|
11
|
-
|
38
|
+
# Initializes a Syntropy error with the given HTTP status and message.
|
39
|
+
#
|
40
|
+
# @param http_status [Integer, String] HTTP status
|
41
|
+
# @param msg [String] error message
|
42
|
+
# @return [void]
|
43
|
+
def initialize(http_status = DEFAULT_STATUS, msg = '')
|
12
44
|
super(msg)
|
13
|
-
@http_status =
|
45
|
+
@http_status = http_status
|
14
46
|
end
|
15
47
|
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
teapot: Status::TEAPOT
|
22
|
-
}
|
23
|
-
.each { |k, v|
|
24
|
-
define_method(k) { |msg = ''| new(v, msg) }
|
25
|
-
}
|
48
|
+
# Returns the HTTP status for the error.
|
49
|
+
#
|
50
|
+
# @return [Integer, String] HTTP status
|
51
|
+
def http_status
|
52
|
+
@http_status || Qeweney::Status::INTERNAL_SERVER_ERROR
|
26
53
|
end
|
27
54
|
end
|
28
55
|
|
56
|
+
# ValidationError is raised when a validation has failed.
|
29
57
|
class ValidationError < Error
|
30
58
|
def initialize(msg)
|
31
59
|
super(Qeweney::Status::BAD_REQUEST, msg)
|
data/lib/syntropy/markdown.rb
CHANGED
@@ -1,5 +1,7 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
require 'yaml'
|
4
|
+
|
3
5
|
module Syntropy
|
4
6
|
DATE_REGEXP = /(\d{4}-\d{2}-\d{2})/
|
5
7
|
FRONT_MATTER_REGEXP = /\A(---\s*\n.*?\n?)^((---|\.\.\.)\s*$\n?)/m
|
@@ -28,9 +30,9 @@ module Syntropy
|
|
28
30
|
atts = atts.merge(yaml)
|
29
31
|
end
|
30
32
|
|
31
|
-
if opts[:
|
33
|
+
if opts[:root_dir]
|
32
34
|
atts[:url] = path
|
33
|
-
.gsub(/#{opts[:
|
35
|
+
.gsub(/#{opts[:root_dir]}/, '')
|
34
36
|
.gsub(/\.md$/, '')
|
35
37
|
end
|
36
38
|
|
data/lib/syntropy/module.rb
CHANGED
@@ -4,8 +4,8 @@ require 'p2'
|
|
4
4
|
|
5
5
|
module Syntropy
|
6
6
|
class ModuleLoader
|
7
|
-
def initialize(
|
8
|
-
@
|
7
|
+
def initialize(env)
|
8
|
+
@root_dir = env[:root_dir]
|
9
9
|
@env = env
|
10
10
|
@loaded = {} # maps ref to code
|
11
11
|
@fn_map = {} # maps filename to ref
|
@@ -26,28 +26,20 @@ module Syntropy
|
|
26
26
|
private
|
27
27
|
|
28
28
|
def load_module(ref)
|
29
|
-
fn = File.expand_path(File.join(@
|
29
|
+
fn = File.expand_path(File.join(@root_dir, "#{ref}.rb"))
|
30
30
|
@fn_map[fn] = ref
|
31
|
-
raise "File not found #{fn}" if !File.file?(fn)
|
32
|
-
|
33
|
-
mod_body = IO.read(fn)
|
34
|
-
mod_ctx = Class.new(Syntropy::Module)
|
35
|
-
mod_ctx.prepare(loader: self, env: @env, ref: ref)
|
36
|
-
mod_ctx.module_eval(mod_body, fn, 1)
|
37
|
-
|
38
|
-
export_value = mod_ctx.__export_value__
|
31
|
+
raise Syntropy::Error, "File not found #{fn}" if !File.file?(fn)
|
39
32
|
|
40
|
-
|
33
|
+
code = IO.read(fn)
|
34
|
+
env = @env.merge(module_loader: self, ref: ref)
|
35
|
+
export_value = Syntropy::Module.load(env, code)
|
36
|
+
transform_module_export_value(export_value)
|
41
37
|
end
|
42
38
|
|
43
|
-
def
|
39
|
+
def transform_module_export_value(export_value)
|
44
40
|
case export_value
|
45
41
|
when nil
|
46
|
-
raise 'No export found'
|
47
|
-
when Symbol
|
48
|
-
o = mod_ctx.new(@env)
|
49
|
-
# TODO: verify export_value denotes a valid method
|
50
|
-
->(req) { o.send(export_value, req) }
|
42
|
+
raise Syntropy::Error, 'No export found'
|
51
43
|
when String
|
52
44
|
->(req) { req.respond(export_value) }
|
53
45
|
when Class
|
@@ -59,74 +51,44 @@ module Syntropy
|
|
59
51
|
end
|
60
52
|
|
61
53
|
class Module
|
62
|
-
def
|
63
|
-
|
54
|
+
def self.load(env, code)
|
55
|
+
m = new(**env)
|
56
|
+
m.instance_eval(code)
|
57
|
+
m.__export_value__
|
64
58
|
end
|
65
59
|
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
60
|
+
attr_reader
|
61
|
+
def initialize(**env)
|
62
|
+
@env = env
|
63
|
+
@machine = env[:machine]
|
64
|
+
@module_loader = env[:module_loader]
|
65
|
+
@app = env[:app]
|
66
|
+
@ref = env[:ref]
|
67
|
+
singleton_class.const_set(:MODULE, self)
|
68
|
+
end
|
69
|
+
|
70
|
+
attr_reader :__export_value__
|
71
|
+
def export(v)
|
72
|
+
@__export_value__ = v
|
73
|
+
end
|
80
74
|
|
81
|
-
|
82
|
-
|
83
|
-
|
75
|
+
def import(ref)
|
76
|
+
@module_loader.load(ref)
|
77
|
+
end
|
84
78
|
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
P2::Template.new(proc)
|
90
|
-
end
|
91
|
-
alias_method :html, :template
|
92
|
-
|
93
|
-
def route_by_host(map = nil)
|
94
|
-
root = @env[:location]
|
95
|
-
sites = Dir[File.join(root, '*')]
|
96
|
-
.reject { File.basename(it) =~ /^_/ }
|
97
|
-
.select { File.directory?(it) }
|
98
|
-
.each_with_object({}) { |fn, h|
|
99
|
-
name = File.basename(fn)
|
100
|
-
opts = @env.merge(location: fn)
|
101
|
-
h[name] = Syntropy::App.new(opts[:machine], opts[:location], opts[:mount_path], opts)
|
102
|
-
}
|
103
|
-
|
104
|
-
map&.each do |k, v|
|
105
|
-
sites[k] = sites[v]
|
106
|
-
end
|
107
|
-
|
108
|
-
lambda { |req|
|
109
|
-
site = sites[req.host]
|
110
|
-
site ? site.call(req) : req.respond(nil, ':status' => Status::BAD_REQUEST)
|
111
|
-
}
|
112
|
-
end
|
79
|
+
def template(proc = nil, &block)
|
80
|
+
proc ||= block
|
81
|
+
raise "No template block/proc given" if !proc
|
113
82
|
|
114
|
-
|
115
|
-
|
116
|
-
raise 'Not a directory' if !File.directory?(full_path)
|
83
|
+
P2::Template.new(proc)
|
84
|
+
end
|
117
85
|
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
}
|
122
|
-
end
|
86
|
+
def page_list(ref)
|
87
|
+
Syntropy.page_list(@env, ref)
|
88
|
+
end
|
123
89
|
|
124
|
-
|
125
|
-
|
126
|
-
mount_path ||= @env[:mount_path]
|
127
|
-
opts = @env.merge(location:, mount_path:)
|
128
|
-
Syntropy::App.new(opts[:machine], opts[:location], opts[:mount_path], opts)
|
129
|
-
end
|
90
|
+
def app(**env)
|
91
|
+
Syntropy::App.new(**(@env.merge(env)))
|
130
92
|
end
|
131
93
|
end
|
132
94
|
end
|