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.
@@ -0,0 +1,550 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Syntropy
4
+ # The RoutingTree class implements a file-based routing tree with support for
5
+ # static files, markdown files, ruby modules, parametric routes, subtree routes,
6
+ # nested middleware and error handlers.
7
+ #
8
+ # A RoutingTree instance takes the given directory (root_dir) and constructs a
9
+ # tree of route entries corresponding to the directory's contents. Finally, it
10
+ # generates an optimized router proc, which is used by the application to return
11
+ # a route entry for each incoming HTTP request.
12
+ #
13
+ # Once initialized, the routing tree is immutable. When running Syntropy in
14
+ # watch mode, whenever a file or directory is changed, added or deleted, a new
15
+ # routing tree will be constructed, and the old one will be discarded.
16
+ #
17
+ # File-based routing in Syntropy follows some simple rules:
18
+ #
19
+ # - Static files (anything other than markdown files or dynamic Ruby modules)
20
+ # are routed to according to their location in the file tree.
21
+ # - Index files with `.md` or `.rb` extension handle requests to their
22
+ # immediate containing directory. For example, `/users/index.rb` will handle
23
+ # requests to `/users`.
24
+ # - Index files with a `+` suffix will also handle requests to anywhere in their
25
+ # subtree. For example, `/users/index+.rb` will also handle requests to
26
+ # `/users/foo/bar`.
27
+ # - Other markdown and module files will handle requests to their bare name
28
+ # (that is, without the extension.) Thus, `/users/foo.rb` will handle requests
29
+ # to `/users/foo`. A route with a `+` suffix will also handle requests to the
30
+ # route's subtree. Thus, `/users/foo+.rb` will also handle requests to
31
+ # `/users/foo/bar`.
32
+ # - Parametric routes are implemented by enclosing the route name in square
33
+ # brackets. For example, `/processes/[proc_id]/index.rb` will handle requests
34
+ # to `/posts/14` etc. Parametric route parts can also be expressed as files,
35
+ # e.g. `/processes/[id]/sources/[src_id].rb` will handle requests to
36
+ # `/posts/14/sources/42` etc. The values for placeholders are added to the
37
+ # incoming request. Here too, a `+` suffix causes the route to also handle
38
+ # requests to its subtree.
39
+ # - Directories and files whose names start with an underscore, e.g. `/_foo` or
40
+ # `/docs/_bar.rb` are skipped and will not be added to the routing tree. This
41
+ # allows you to prevent access through the HTTP server to protected or
42
+ # internal modules or files.
43
+ class RoutingTree
44
+ attr_reader :root_dir, :mount_path, :static_map, :dynamic_map, :root
45
+
46
+ # Initializes a new RoutingTree instance and computes the routing tree
47
+ #
48
+ # @param root_dir [String] root directory of file tree
49
+ # @param mount_path [String] base URL path
50
+ # @return [void]
51
+ def initialize(root_dir:, mount_path:, **env)
52
+ @root_dir = root_dir
53
+ @mount_path = mount_path
54
+ @static_map = {}
55
+ @dynamic_map = {}
56
+ @env = env
57
+ @root = compute_tree
58
+ @static_map.freeze
59
+ @dynamic_map.freeze
60
+ end
61
+
62
+ # Returns the generated router proc for the routing tree
63
+ #
64
+ # @return [Proc] router proc
65
+ def router_proc
66
+ @router_proc ||= compile_router_proc
67
+ end
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
+ # Computes a "clean" URL path for the given path. Modules and markdown are
96
+ # stripped of their extensions, and index file paths are also converted to the
97
+ # containing directory path. For example, the clean URL path for `/foo/bar.rb`
98
+ # is `/foo/bar`. The Clean URL path for `/bar/baz/index.rb` is `/bar/baz`.
99
+ #
100
+ # @param fn [String] file path
101
+ # @return [String] clean path
102
+ def compute_clean_url_path(fn)
103
+ rel_path = fn.sub(@root_dir, '')
104
+ case rel_path
105
+ when /^(.*)\/index\.(md|rb|html)$/
106
+ Regexp.last_match(1).then { it == '' ? '/' : it }
107
+ when /^(.*)\.(md|rb|html)$/
108
+ Regexp.last_match(1)
109
+ else
110
+ rel_path
111
+ end
112
+ end
113
+
114
+ # Converts filename to relative path.
115
+ #
116
+ # @param fn [String] filename
117
+ # @return [String] relative path
118
+ def fn_to_rel_path(fn)
119
+ fn.sub(/^#{Regexp.escape(@root_dir)}\//, '').sub(/\.[^\.]+$/, '')
120
+ end
121
+
122
+ private
123
+
124
+ # Maps extensions to route kind.
125
+ FILE_TYPE = {
126
+ '.rb' => :module,
127
+ '.md' => :markdown
128
+ }
129
+
130
+ # Computes the routing tree, returning the root entry. Route entries are
131
+ # represented as hashes with the following keys:
132
+ #
133
+ # - `:parent` - reference to the parent entry.
134
+ # - `:path` - the URL path for the entry.
135
+ # - `:target` - a hash containing route target information.
136
+ # - `:param` - the parameter name for parametric routes.
137
+ # - `:hook` - a reference to the hook module (`_hook.rb`) for the directory,
138
+ # if exists.
139
+ # - `:error` - a reference to the error handler module (`_error.rb`) for the
140
+ # directory, if exists.
141
+ # - `children` - a hash mapping segment names to the corresponding child
142
+ # entries.
143
+ #
144
+ # Route entries are created for any directory, and for any *dynamic* files
145
+ # (i.e. markdown or Ruby module files). Files starting with `_` are not
146
+ # considered as routes and will not be included in the routing tree. Static
147
+ # files will also not be included in the routing tree, but instead will be
148
+ # mapped in the static file map (see below).
149
+ #
150
+ # The routing tree is complemented with two maps:
151
+ #
152
+ # - `static_map` - maps URL paths to the corresponding static route entries,
153
+ # which includes all non-parametric routes, as well as all static files.
154
+ # - `dynamic_map` - maps URL paths to the corresponding parametric route
155
+ # entries.
156
+ #
157
+ # The reason we use two separate maps is to prevent accidentally hitting a
158
+ # false lookup for a a URL with segments containing square brackets!
159
+ #
160
+ # @return [Hash] root entry
161
+ def compute_tree
162
+ compute_route_directory(dir: @root_dir, rel_path: '/', parent: nil)
163
+ end
164
+
165
+ # Computes a route entry for a directory.
166
+ #
167
+ # @param dir [String] directory path
168
+ # @param rel_path [String] relative directory path
169
+ # @param parent [Hash, nil] parent entry
170
+ def compute_route_directory(dir:, rel_path:, parent: nil)
171
+ param = (m = File.basename(dir).match(/^\[(.+)\]$/)) ? m[1] : nil
172
+ entry = {
173
+ parent:,
174
+ path: rel_path_to_abs_path(rel_path),
175
+ param:,
176
+ hook: find_aux_module_entry(dir, '_hook.rb'),
177
+ error: find_aux_module_entry(dir, '_error.rb')
178
+ }
179
+ entry[:children] = compute_child_routes(
180
+ dir:, rel_path:, parent: entry
181
+ )
182
+ entry
183
+ end
184
+
185
+ # Searches for a file of the given name in the given directory. If found,
186
+ # returns the file path.
187
+ #
188
+ # @param dir [String] directory path
189
+ # @param name [String] filename
190
+ # @return [String, nil] file path if found
191
+ def find_aux_module_entry(dir, name)
192
+ fn = File.join(dir, name)
193
+ File.file?(fn) ? ({ kind: :module, fn: }) : nil
194
+ end
195
+
196
+ # Returns a hash mapping file/dir names to route entries.
197
+ #
198
+ # @param dir [String] directory path to scan for files
199
+ # @param rel_path [String] directory path relative to root directory
200
+ # @param parent [Hash] directory's corresponding route entry
201
+ def compute_child_routes(dir:, rel_path:, parent:)
202
+ file_search(dir).each_with_object({}) { |fn, map|
203
+ next if File.basename(fn) =~ /^_/
204
+
205
+ rel_path = compute_clean_url_path(fn)
206
+ child = if File.file?(fn)
207
+ compute_route_file(fn:, rel_path:, parent:)
208
+ elsif File.directory?(fn)
209
+ compute_route_directory(dir: fn, rel_path:, parent:)
210
+ end
211
+ map[child_key(child)] = child if child
212
+ }
213
+ end
214
+
215
+ # Returns all entries in the given dir.
216
+ #
217
+ # @param dir [String] directory path
218
+ # @return [Array<String>] array of file entries
219
+ def file_search(dir)
220
+ Dir[File.join(dir.gsub(/[\[\]]/) { "\\#{it}"}, '*')]
221
+ end
222
+
223
+ # Computes a route entry and/or target for the given file path.
224
+ #
225
+ # @param fn [String] file path
226
+ # @param rel_path [String] relative path
227
+ # @param parent [Hash, nil] parent entry
228
+ # @return [void]
229
+ def compute_route_file(fn:, rel_path:, parent:)
230
+ abs_path = rel_path_to_abs_path(rel_path)
231
+
232
+ # index.rb, index+.rb, index.md
233
+ case
234
+ when (m = fn.match(/\/index(\+)?(\.(?:rb|md))$/))
235
+ make_index_module_route(m:, parent:, path: abs_path, fn:)
236
+
237
+ # index.html
238
+ when fn.match(/\/index\.html$/)
239
+ set_index_route_target(parent:, path: abs_path, kind: :static, fn:)
240
+
241
+ # foo.rb, foo+.rb, foo.md, [foo].rb, [foo]+.rb
242
+ when (m = fn.match(/\/(\[)?([^\]\/\+]+)(\])?(\+)?(\.(?:rb|md))$/))
243
+ make_module_route(m:, parent:, path: abs_path, fn:)
244
+
245
+ # everything else
246
+ else
247
+ # static files resolved using the static map, and are not added to the
248
+ # routing tree, which is used for resolving dynamic routes. HTML files
249
+ # are routed by their clean URL, i.e. without the `.html` extension.
250
+ target = { kind: :static, fn: }
251
+ make_route_entry(parent:, path: abs_path, target:)
252
+ end
253
+ end
254
+
255
+ # Creates a route entry for an index module (ruby/markdown). Index files
256
+ # (modules or markdown) files) are applied as targets to the immediate
257
+ # containing directory. A + suffix indicates this route handles requests to
258
+ # its subtree
259
+ #
260
+ # @param m [MatchData] path match data
261
+ # @param parent [Hash] parent route entry
262
+ # @param path [String] route path
263
+ # @param fn [String] route target filename
264
+ # @return [nil] (prevents addition of an index route)
265
+ def make_index_module_route(m:, parent:, path:, fn:)
266
+ plus, ext = m[1..2]
267
+ kind = FILE_TYPE[ext]
268
+ handle_subtree = (plus == '+') && (kind == :module)
269
+ set_index_route_target(parent:, path:, kind:, fn:, handle_subtree:)
270
+ end
271
+
272
+
273
+ # Sets an index route target for the given parent entry. Index files are
274
+ # applied as targets to the immediate containing directory. HTML index files
275
+ # are considered static and therefore not added to the routing tree.
276
+ #
277
+ # @param parent [Hash] parent route entry
278
+ # @param path [String] route path
279
+ # @param kind [Symbol] route target kind
280
+ # @param fn [String] route target filename
281
+ # @param handle_subtree [bool] whether the target handles the subtree
282
+ # @return [nil] (prevents addition of an index route)
283
+ def set_index_route_target(parent:, path:, kind:, fn:, handle_subtree: nil)
284
+ if is_parametric_route?(parent) || handle_subtree
285
+ @dynamic_map[path] = parent
286
+ parent[:target] = { kind:, fn: }
287
+ parent[:handle_subtree] = handle_subtree
288
+ else
289
+ @static_map[path] = {
290
+ parent: parent[:parent],
291
+ path:,
292
+ target: { kind:, fn: },
293
+ # In case we're at the tree root, we need to copy over the hook and
294
+ # error refs.
295
+ hook: !parent[:parent] && parent[:hook],
296
+ error: !parent[:parent] && parent[:error]
297
+ }
298
+ end
299
+ nil
300
+ end
301
+
302
+ # Creates a route entry for normal module and markdown files. A + suffix
303
+ # indicates the module also handles requests to the subtree. For example,
304
+ # `/foo/bar.rb` will handle requests to `/foo/bar`, but `/foo/bar+.rb` will
305
+ # also handle requests to `/foo/bar/baz/bug`.
306
+ #
307
+ # parametric, or wildcard, routes convert segments of the URL path into
308
+ # parameters that are added to the HTTP request. Parametric routes are
309
+ # denoted using square brackets around the file/directory name. For example,
310
+ # `/api/posts/[id].rb`` will handle requests to `/api/posts/42`, and will
311
+ # extract the parameter `posts => 42` to add to the incoming request.
312
+ #
313
+ # A + suffix indicates the module also handles the subtree, so e.g.
314
+ # `/api/posts/[id]+.rb` will also handle requests to `/api/posts/42/fans`
315
+ # etc.
316
+ #
317
+ # @param m [MatchData] path match data
318
+ # @param parent [Hash] parent route entry
319
+ # @param path [String] route path
320
+ # @param fn [String] route target filename
321
+ # @return [Hash] route entry
322
+ def make_module_route(m:, parent:, path:, fn:)
323
+ ob, param, cb, plus, ext = m[1..5]
324
+ kind = FILE_TYPE[ext]
325
+ make_route_entry(
326
+ parent:, path:, param: ob && cb ? param : nil,
327
+ target: { kind:, fn: },
328
+ handle_subtree: (plus == '+') && (kind == :module)
329
+ )
330
+ end
331
+
332
+ # Creates a new route entry, registering it in the static or dynamic map,
333
+ # according to its type.
334
+ #
335
+ # @param entry [Hash] route entry
336
+ def make_route_entry(entry)
337
+ path = entry[:path]
338
+ if is_parametric_route?(entry) || entry[:handle_subtree]
339
+ @dynamic_map[path] = entry
340
+ else
341
+ entry[:static] = true
342
+ @static_map[path] = entry
343
+ end
344
+ end
345
+
346
+ # returns true if the route or any of its ancestors are parametric.
347
+ #
348
+ # @param entry [Hash] route entry
349
+ def is_parametric_route?(entry)
350
+ entry[:param] || (entry[:parent] && is_parametric_route?(entry[:parent]))
351
+ end
352
+
353
+ # Converts a relative URL path to absolute URL path.
354
+ #
355
+ # @param rel_path [String] relative path
356
+ # @return [String] absolute path
357
+ def rel_path_to_abs_path(rel_path)
358
+ rel_path == '/' ? @mount_path : File.join(@mount_path, rel_path)
359
+ end
360
+
361
+ # Returns the key for the given route entry to be used in its parent's
362
+ # children map.
363
+ #
364
+ # @param entry [Hash] route entry
365
+ # @return [String] child key
366
+ def child_key(entry)
367
+ entry[:param] ? '[]' : File.basename(entry[:path]).gsub(/\+$/, '')
368
+ end
369
+
370
+ # Generates and returns a router proc based on the routing tree.
371
+ #
372
+ # @return [Proc] router proc
373
+ def compile_router_proc
374
+ code = generate_routing_tree_code
375
+ eval(code, binding, '(router)', 1)
376
+ end
377
+
378
+ # Generates the router proc source code. The router proc code is dynamically
379
+ # generated from the routing tree, converting the routing tree structure into
380
+ # Ruby proc of the following signature:
381
+ #
382
+ # ```ruby
383
+ # # @param path [String] URL path
384
+ # # @param params [Hash] Hash receiving parametric route values
385
+ # # @return [Hash, nil] route entry
386
+ # ->(path, params) { ... }
387
+ # ```
388
+ #
389
+ # The generated code performs the following tasks:
390
+ #
391
+ # - Test if the given path corresponds to a static file (using `@static_map`)
392
+ # - Otherwise, split the given path into path segments
393
+ # - Walk through the path segments according to the routing tree structure
394
+ # - Emit parametric route values to the `params` hash
395
+ # - Return the found route entry
396
+ #
397
+ # @return [String] router proc code to be `eval`ed
398
+ def generate_routing_tree_code
399
+ buffer = +''
400
+ buffer << "# frozen_string_literal: true\n"
401
+
402
+ emit_code_line(buffer, '->(path, params) {')
403
+ emit_code_line(buffer, ' entry = @static_map[path]; return entry if entry')
404
+ emit_code_line(buffer, ' parts = path.split("/")')
405
+
406
+ if @root[:path] != '/'
407
+ root_parts = @root[:path].split('/')
408
+ segment_idx = root_parts.size
409
+ validate_parts = []
410
+ (1..(segment_idx - 1)).each do |i|
411
+ validate_parts << "(parts[#{i}] != #{root_parts[i].inspect})"
412
+ end
413
+ emit_code_line(buffer, " return nil if #{validate_parts.join(' || ')}")
414
+ else
415
+ segment_idx = 1
416
+ end
417
+
418
+ visit_routing_tree_entry(buffer:, entry: @root, segment_idx:)
419
+
420
+ emit_code_line(buffer, " return nil")
421
+ emit_code_line(buffer, "}")
422
+ buffer#.tap { puts '*' * 40; puts it; puts }
423
+ end
424
+
425
+ # Generates routing logic code for the given route entry.
426
+ #
427
+ # @param buffer [String] buffer receiving code
428
+ # @param entry [Hash] route entry
429
+ # @param indent [Integer] indent level
430
+ # @param segment_idx [Integer] path segment index
431
+ # @return [void]
432
+ def visit_routing_tree_entry(buffer:, entry:, indent: 1, segment_idx:)
433
+ ws = ' ' * (indent * 2)
434
+
435
+ # If no targets exist in the entry's subtree, we can return nil
436
+ # immediately.
437
+ if !entry[:target] && !find_target_in_subtree(entry)
438
+ emit_code_line(buffer, "#{ws}return nil")
439
+ return
440
+ end
441
+
442
+ if is_void_route?(entry)
443
+ parent = entry[:parent]
444
+ parametric_sibling = parent && parent[:children] && parent[:children]['[]']
445
+ if parametric_sibling
446
+ emit_code_line(buffer, "#{ws}return nil")
447
+ return
448
+ end
449
+ end
450
+
451
+ # Get next segment
452
+ emit_code_line(buffer, "#{ws}case (p = parts[#{segment_idx}])")
453
+
454
+ # In case of no next segment
455
+ emit_code_line(buffer, "#{ws}when nil")
456
+ if entry[:target]
457
+ map = entry[:static] ? '@static_map' : '@dynamic_map'
458
+ emit_code_line(buffer, "#{ws} return #{map}[#{entry[:path].inspect}]")
459
+ else
460
+ emit_code_line(buffer, "#{ws} return nil")
461
+ end
462
+
463
+ if entry[:children]
464
+ param_entry = entry[:children]['[]']
465
+ entry[:children].each do |k, child_entry|
466
+ # skip if wildcard entry (treated in else clause below)
467
+ next if k == '[]'
468
+
469
+ # skip if entry is void (no target, no children)
470
+ has_target = child_entry[:target]
471
+ has_children = child_entry[:children] && !child_entry[:children].empty?
472
+ next if !has_target && !has_children
473
+
474
+ if has_target && !has_children
475
+ # use the target
476
+ next if child_entry[:static]
477
+
478
+ emit_code_line(buffer, "#{ws}when #{k.inspect}")
479
+ if_clause = child_entry[:handle_subtree] ? '' : " if !parts[#{segment_idx + 1}]"
480
+ route_value = "@dynamic_map[#{child_entry[:path].inspect}]"
481
+ emit_code_line(buffer, "#{ws} return #{route_value}#{if_clause}")
482
+
483
+ elsif has_children
484
+ # otherwise look at the next segment
485
+ next if is_void_route?(child_entry) && !param_entry
486
+
487
+ emit_code_line(buffer, "#{ws}when #{k.inspect}")
488
+ visit_routing_tree_entry(buffer:, entry: child_entry, indent: indent + 1, segment_idx: segment_idx + 1)
489
+ end
490
+ end
491
+
492
+ # parametric route
493
+ if param_entry
494
+ emit_code_line(buffer, "#{ws}else")
495
+ emit_code_line(buffer, "#{ws} params[#{param_entry[:param].inspect}] = p")
496
+ visit_routing_tree_entry(buffer:, entry: param_entry, indent: indent + 1, segment_idx: segment_idx + 1)
497
+ end
498
+ end
499
+ emit_code_line(buffer, "#{ws}end")
500
+ end
501
+
502
+ # Returns the first target found in the given entry's subtree.
503
+ #
504
+ # @param entry [Hash] route entry
505
+ # @return [Hash, nil] route target if exists
506
+ def find_target_in_subtree(entry)
507
+ entry[:children]&.values&.each { |e|
508
+ target = e[:target] || find_target_in_subtree(e)
509
+ return target if target
510
+ }
511
+
512
+ nil
513
+ end
514
+
515
+ # Returns true if the given route is not parametric, has no children and is
516
+ # static, or has children and all are void.
517
+ #
518
+ # @param entry [Hash] route entry
519
+ # @return [bool]
520
+ def is_void_route?(entry)
521
+ return false if entry[:param]
522
+
523
+ if entry[:children]
524
+ return true if !entry[:children]['[]'] && entry[:children]&.values&.all? { is_void_route?(it) }
525
+ else
526
+ return true if entry[:static]
527
+ end
528
+
529
+ false
530
+ end
531
+
532
+ DEBUG = !!ENV['DEBUG']
533
+
534
+ # Emits the given code into the given buffer, with a line break at the end.
535
+ # If the `DEBUG` environment variable is set, adds a source location comment
536
+ # at the end of the line, referencing the callsite.
537
+ #
538
+ # @param buffer [String] code buffer
539
+ # @param code [String] code
540
+ # @return [void]
541
+ def emit_code_line(buffer, code)
542
+ if DEBUG
543
+ loc = (m = caller[0].match(/^([^\:]+\:\d+)/)) && m[1]
544
+ buffer << "#{code} # #{loc}\n"
545
+ else
546
+ buffer << "#{code}\n"
547
+ end
548
+ end
549
+ end
550
+ end
@@ -0,0 +1,42 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Syntropy
4
+ # Utilities for use in modules
5
+ module Utilities
6
+ # Returns a request handler that routes request according to
7
+ def route_by_host(env, map = nil)
8
+ root = env[:root_dir]
9
+ sites = Dir[File.join(root, '*')]
10
+ .reject { File.basename(it) =~ /^_/ }
11
+ .select { File.directory?(it) }
12
+ .each_with_object({}) { |fn, h|
13
+ name = File.basename(fn)
14
+ opts = opts.merge(root_dir: fn)
15
+ h[name] = Syntropy::App.new(**opts)
16
+ }
17
+
18
+ # copy over map refs
19
+ map&.each { |k, v| sites[k] = sites[v] }
20
+
21
+ #
22
+ lambda { |req|
23
+ site = sites[req.host]
24
+ site ? site.call(req) : req.respond(nil, ':status' => Status::BAD_REQUEST)
25
+ }
26
+ end
27
+
28
+ def page_list(env, ref)
29
+ full_path = File.join(env[:root_dir], ref)
30
+ raise 'Not a directory' if !File.directory?(full_path)
31
+
32
+ Dir[File.join(full_path, '*.md')].sort.map {
33
+ atts, markdown = Syntropy.parse_markdown_file(it, env)
34
+ { atts:, markdown: }
35
+ }
36
+ end
37
+
38
+ def app(**env)
39
+ Syntropy::App.new(**env)
40
+ end
41
+ end
42
+ end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Syntropy
4
- VERSION = '0.11'
4
+ VERSION = '0.13'
5
5
  end
data/lib/syntropy.rb CHANGED
@@ -12,13 +12,16 @@ require 'syntropy/errors'
12
12
  require 'syntropy/markdown'
13
13
  require 'syntropy/module'
14
14
  require 'syntropy/request_extensions'
15
- require 'syntropy/router'
15
+ require 'syntropy/routing_tree'
16
16
  require 'syntropy/rpc_api'
17
17
  require 'syntropy/side_run'
18
+ require 'syntropy/utils'
18
19
 
19
20
  module Syntropy
20
21
  Status = Qeweney::Status
21
22
 
23
+ extend Utilities
24
+
22
25
  class << self
23
26
  attr_accessor :machine
24
27
 
data/syntropy.gemspec CHANGED
@@ -25,7 +25,7 @@ Gem::Specification.new do |s|
25
25
  s.add_dependency 'json', '2.13.2'
26
26
  s.add_dependency 'p2', '2.8'
27
27
  s.add_dependency 'papercraft', '1.4'
28
- s.add_dependency 'qeweney', '0.21'
28
+ s.add_dependency 'qeweney', '0.22'
29
29
  s.add_dependency 'tp2', '0.14.1'
30
30
  s.add_dependency 'uringmachine', '0.16'
31
31
 
@@ -10,4 +10,4 @@ def bar
10
10
  @env[:baz]
11
11
  end
12
12
 
13
- export :call
13
+ export self
@@ -0,0 +1,21 @@
1
+ def env
2
+ @env
3
+ end
4
+
5
+ def machine
6
+ @machine
7
+ end
8
+
9
+ def module_loader
10
+ @module_loader
11
+ end
12
+
13
+ def app
14
+ @app
15
+ end
16
+
17
+ def module_const
18
+ MODULE
19
+ end
20
+
21
+ export MODULE
@@ -0,0 +1,3 @@
1
+ export ->(req) {
2
+ req.respond_on_get("#{req.route[:path]}-#{req.route_params['foo']}")
3
+ }
data/test/app/tmp.rb CHANGED
@@ -3,4 +3,4 @@
3
3
  def call(req)
4
4
  req.respond('foo')
5
5
  end
6
- export :call
6
+ export self
@@ -1 +1 @@
1
- export route_by_host
1
+ export Syntropy.route_by_host(@env)