syntropy 0.20 → 0.21
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 +4 -0
- data/lib/syntropy/app.rb +3 -1
- data/lib/syntropy/routing_tree.rb +123 -50
- data/lib/syntropy/version.rb +1 -1
- data/test/test_routing_tree.rb +164 -0
- metadata +1 -1
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 8069c4fe6a21289babda24dae14b580c5ea476a5d77bdcb09e31ae72d751459c
|
4
|
+
data.tar.gz: de80dbc652706f64819cef25d23ca81f19ed0e628d2860ae01a0e5792ea1bc6d
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: ec0769f1619a71b926a19c56499b28c4e2331d05d417c8d0d6d3b21c1804a2ea2e52c93307a716c289c381b617bb66fcd174bb92b0c9b612f80f7561b32f0bba
|
7
|
+
data.tar.gz: b836b5a0bd012ae3a179c515cb177959723da53e99c0bb2212a1b5caab6270b38a9a33282b73279a6915ac768324cd61604ce6db02237f33b55980145c87f5e8
|
data/CHANGELOG.md
CHANGED
data/lib/syntropy/app.rb
CHANGED
@@ -110,7 +110,9 @@ module Syntropy
|
|
110
110
|
@router_proc = @routing_tree.router_proc
|
111
111
|
end
|
112
112
|
|
113
|
-
|
113
|
+
# Mounts the builtin applet on the routing tree.
|
114
|
+
#
|
115
|
+
# @return [void]
|
114
116
|
def mount_builtin_applet
|
115
117
|
path = @env[:builtin_applet_path]
|
116
118
|
@builtin_applet ||= Syntropy.builtin_applet(@env, mount_path: path)
|
@@ -309,6 +309,10 @@ module Syntropy
|
|
309
309
|
plus, ext = m[1..2]
|
310
310
|
kind = FILE_TYPE[ext]
|
311
311
|
handle_subtree = (plus == '+') && (kind == :module)
|
312
|
+
if handle_subtree
|
313
|
+
path = path.gsub(/\/index\+$/, '')
|
314
|
+
path = '/' if path.empty?
|
315
|
+
end
|
312
316
|
set_index_route_target(parent:, path:, kind:, fn:, handle_subtree:)
|
313
317
|
end
|
314
318
|
|
@@ -443,29 +447,79 @@ module Syntropy
|
|
443
447
|
# @return [String] router proc code to be `eval`ed
|
444
448
|
def generate_routing_tree_code
|
445
449
|
buffer = +''
|
446
|
-
buffer << "# frozen_string_literal: true\n"
|
450
|
+
buffer << "# frozen_string_literal: true\n\n"
|
447
451
|
|
448
|
-
|
449
|
-
|
450
|
-
emit_code_line(buffer, ' parts = path.split("/")')
|
452
|
+
wildcard_root = @root[:handle_subtree]
|
453
|
+
childless_root = !@root[:children] || @root[:children].empty?
|
451
454
|
|
452
|
-
if
|
453
|
-
|
454
|
-
segment_idx = root_parts.size
|
455
|
-
validate_parts = []
|
456
|
-
(1..(segment_idx - 1)).each do |i|
|
457
|
-
validate_parts << "(parts[#{i}] != #{root_parts[i].inspect})"
|
458
|
-
end
|
459
|
-
emit_code_line(buffer, " return nil if #{validate_parts.join(' || ')}")
|
455
|
+
if wildcard_root && childless_root
|
456
|
+
emit_wildcard_childless_root_code(buffer, @root[:path])
|
460
457
|
else
|
458
|
+
emit_router_proc_prelude(buffer)
|
461
459
|
segment_idx = 1
|
460
|
+
if @root[:path] != '/'
|
461
|
+
root_parts = @root[:path].split('/')
|
462
|
+
segment_idx = root_parts.size
|
463
|
+
emit_root_validate_guard(buffer:, root_parts:)
|
464
|
+
end
|
465
|
+
|
466
|
+
visit_routing_tree_entry(buffer:, entry: @root, segment_idx:)
|
467
|
+
emit_router_proc_postlude(buffer, default_route_path: wildcard_root && @root[:path])
|
462
468
|
end
|
463
469
|
|
464
|
-
|
470
|
+
buffer#.tap { puts '*' * 40; puts it; puts }
|
471
|
+
end
|
472
|
+
|
473
|
+
# Emits optimized code for a childless wildcard router.
|
474
|
+
#
|
475
|
+
# @param buffer [String] output buffer
|
476
|
+
# @param root_path [String] router root path
|
477
|
+
# @return [void]
|
478
|
+
def emit_wildcard_childless_root_code(buffer, root_path)
|
479
|
+
emit_code_line(buffer, '->(path, params) {')
|
480
|
+
if root_path != '/'
|
481
|
+
re = /^#{Regexp.escape(root_path)}(\/.*)?$/
|
482
|
+
emit_code_line(buffer, " return if path !~ #{re.inspect}")
|
483
|
+
end
|
484
|
+
emit_code_line(buffer, " @dynamic_map[#{root_path.inspect}]")
|
485
|
+
emit_code_line(buffer, '}')
|
486
|
+
end
|
487
|
+
|
488
|
+
# Emits router proc prelude code.
|
489
|
+
#
|
490
|
+
# @param buffer [String] output buffer
|
491
|
+
# @return [void]
|
492
|
+
def emit_router_proc_prelude(buffer)
|
493
|
+
emit_code_line(buffer, '->(path, params) {')
|
494
|
+
emit_code_line(buffer, ' entry = @static_map[path]; return entry if entry')
|
495
|
+
emit_code_line(buffer, ' parts = path.split("/")')
|
496
|
+
end
|
497
|
+
|
498
|
+
# Emits root path validation guard code.
|
499
|
+
#
|
500
|
+
# @param buffer [String] output buffer
|
501
|
+
# @param root_parts [Array<String>] root path parts
|
502
|
+
# @return [void]
|
503
|
+
def emit_root_validate_guard(buffer:, root_parts:)
|
504
|
+
validate_parts = []
|
505
|
+
(1...root_parts.size).each do |i|
|
506
|
+
validate_parts << "(parts[#{i}] != #{root_parts[i].inspect})"
|
507
|
+
end
|
508
|
+
emit_code_line(buffer, " return nil if #{validate_parts.join(' || ')}")
|
509
|
+
end
|
465
510
|
|
466
|
-
|
511
|
+
# Emits router proc postlude code.
|
512
|
+
#
|
513
|
+
# @param buffer [String] output buffer
|
514
|
+
# @param default_route_path [String, nil] default route path
|
515
|
+
# @return [void]
|
516
|
+
def emit_router_proc_postlude(buffer, default_route_path:)
|
517
|
+
if default_route_path
|
518
|
+
emit_code_line(buffer, " return @dynamic_map[#{default_route_path.inspect}]")
|
519
|
+
else
|
520
|
+
emit_code_line(buffer, " return nil")
|
521
|
+
end
|
467
522
|
emit_code_line(buffer, "}")
|
468
|
-
buffer#.tap { puts '*' * 40; puts it; puts }
|
469
523
|
end
|
470
524
|
|
471
525
|
# Generates routing logic code for the given route entry.
|
@@ -507,40 +561,7 @@ module Syntropy
|
|
507
561
|
end
|
508
562
|
|
509
563
|
if entry[:children]
|
510
|
-
|
511
|
-
entry[:children].each do |k, child_entry|
|
512
|
-
# skip if wildcard entry (treated in else clause below)
|
513
|
-
next if k == '[]'
|
514
|
-
|
515
|
-
# skip if entry is void (no target, no children)
|
516
|
-
has_target = child_entry[:target]
|
517
|
-
has_children = child_entry[:children] && !child_entry[:children].empty?
|
518
|
-
next if !has_target && !has_children
|
519
|
-
|
520
|
-
if has_target && !has_children
|
521
|
-
# use the target
|
522
|
-
next if child_entry[:static]
|
523
|
-
|
524
|
-
emit_code_line(buffer, "#{ws}when #{k.inspect}")
|
525
|
-
if_clause = child_entry[:handle_subtree] ? '' : " if !parts[#{segment_idx + 1}]"
|
526
|
-
route_value = "@dynamic_map[#{child_entry[:path].inspect}]"
|
527
|
-
emit_code_line(buffer, "#{ws} return #{route_value}#{if_clause}")
|
528
|
-
|
529
|
-
elsif has_children
|
530
|
-
# otherwise look at the next segment
|
531
|
-
next if is_void_route?(child_entry) && !param_entry
|
532
|
-
|
533
|
-
emit_code_line(buffer, "#{ws}when #{k.inspect}")
|
534
|
-
visit_routing_tree_entry(buffer:, entry: child_entry, indent: indent + 1, segment_idx: segment_idx + 1)
|
535
|
-
end
|
536
|
-
end
|
537
|
-
|
538
|
-
# parametric route
|
539
|
-
if param_entry
|
540
|
-
emit_code_line(buffer, "#{ws}else")
|
541
|
-
emit_code_line(buffer, "#{ws} params[#{param_entry[:param].inspect}] = p")
|
542
|
-
visit_routing_tree_entry(buffer:, entry: param_entry, indent: indent + 1, segment_idx: segment_idx + 1)
|
543
|
-
end
|
564
|
+
emit_routing_tree_entry_children_clauses(buffer:, entry:, indent:, segment_idx:)
|
544
565
|
end
|
545
566
|
emit_code_line(buffer, "#{ws}end")
|
546
567
|
end
|
@@ -564,7 +585,7 @@ module Syntropy
|
|
564
585
|
# @param entry [Hash] route entry
|
565
586
|
# @return [bool]
|
566
587
|
def is_void_route?(entry)
|
567
|
-
return false if entry[:param]
|
588
|
+
return false if entry[:param] || entry[:target]
|
568
589
|
|
569
590
|
if entry[:children]
|
570
591
|
return true if !entry[:children]['[]'] && entry[:children]&.values&.all? { is_void_route?(it) }
|
@@ -575,6 +596,58 @@ module Syntropy
|
|
575
596
|
false
|
576
597
|
end
|
577
598
|
|
599
|
+
# Emits case clauses for the given entry's children.
|
600
|
+
#
|
601
|
+
# @param buffer [String] output buffer
|
602
|
+
# @param entry [Hash] route entry
|
603
|
+
# @param indent [Integer] indent level
|
604
|
+
# @param segment_idx [Integer] path segment index
|
605
|
+
# @return [void]
|
606
|
+
def emit_routing_tree_entry_children_clauses(buffer:, entry:, indent:, segment_idx:)
|
607
|
+
ws = ' ' * (indent * 2)
|
608
|
+
|
609
|
+
param_entry = entry[:children]['[]']
|
610
|
+
entry[:children].each do |k, child_entry|
|
611
|
+
# skip if wildcard entry (treated in else clause below)
|
612
|
+
next if k == '[]'
|
613
|
+
|
614
|
+
# skip if entry is void (no target, no children)
|
615
|
+
has_target = child_entry[:target]
|
616
|
+
has_children = child_entry[:children] && !child_entry[:children].empty?
|
617
|
+
next if !has_target && !has_children
|
618
|
+
|
619
|
+
if has_target && !has_children
|
620
|
+
# use the target
|
621
|
+
next if child_entry[:static]
|
622
|
+
|
623
|
+
emit_code_line(buffer, "#{ws}when #{k.inspect}")
|
624
|
+
if_clause = child_entry[:handle_subtree] ? '' : " if !parts[#{segment_idx + 1}]"
|
625
|
+
|
626
|
+
child_path = child_entry[:path]
|
627
|
+
route_value = "@dynamic_map[#{child_path.inspect}]"
|
628
|
+
emit_code_line(buffer, "#{ws} return #{route_value}#{if_clause}")
|
629
|
+
|
630
|
+
elsif has_children
|
631
|
+
# otherwise look at the next segment
|
632
|
+
next if is_void_route?(child_entry) && !param_entry
|
633
|
+
|
634
|
+
emit_code_line(buffer, "#{ws}when #{k.inspect}")
|
635
|
+
visit_routing_tree_entry(buffer:, entry: child_entry, indent: indent + 1, segment_idx: segment_idx + 1)
|
636
|
+
end
|
637
|
+
end
|
638
|
+
|
639
|
+
# parametric route
|
640
|
+
if param_entry
|
641
|
+
emit_code_line(buffer, "#{ws}else")
|
642
|
+
emit_code_line(buffer, "#{ws} params[#{param_entry[:param].inspect}] = p")
|
643
|
+
visit_routing_tree_entry(buffer:, entry: param_entry, indent: indent + 1, segment_idx: segment_idx + 1)
|
644
|
+
# wildcard route
|
645
|
+
elsif entry[:handle_subtree]
|
646
|
+
emit_code_line(buffer, "#{ws}else")
|
647
|
+
emit_code_line(buffer, "#{ws} return @dynamic_map[#{entry[:path].inspect}]")
|
648
|
+
end
|
649
|
+
end
|
650
|
+
|
578
651
|
DEBUG = !!ENV['DEBUG']
|
579
652
|
|
580
653
|
# Emits the given code into the given buffer, with a line break at the end.
|
data/lib/syntropy/version.rb
CHANGED
data/test/test_routing_tree.rb
CHANGED
@@ -408,3 +408,167 @@ class RoutingTreeTest < Minitest::Test
|
|
408
408
|
}
|
409
409
|
end
|
410
410
|
end
|
411
|
+
|
412
|
+
class RoutingTreeWildcardIndexTest < Minitest::Test
|
413
|
+
def test_wildcard_root_index_routing_on_docs
|
414
|
+
file_tree = {
|
415
|
+
'site': {
|
416
|
+
'index+.rb': '',
|
417
|
+
}
|
418
|
+
}
|
419
|
+
|
420
|
+
@root_dir = "/tmp/#{__FILE__.gsub('/', '-')}-#{SecureRandom.hex}"
|
421
|
+
make_tmp_file_tree(@root_dir, file_tree)
|
422
|
+
@rt = Syntropy::RoutingTree.new(root_dir: File.join(@root_dir, 'site'), mount_path: '/docs')
|
423
|
+
|
424
|
+
router = @rt.router_proc
|
425
|
+
|
426
|
+
route = router.('/docs', {})
|
427
|
+
assert_equal '/docs', route[:path]
|
428
|
+
|
429
|
+
route = router.('/docs/foo', {})
|
430
|
+
assert_equal '/docs', route[:path]
|
431
|
+
|
432
|
+
route = router.('/docs/foo/bar', {})
|
433
|
+
assert_equal '/docs', route[:path]
|
434
|
+
|
435
|
+
route = router.('/docsa', {})
|
436
|
+
assert_nil route
|
437
|
+
end
|
438
|
+
|
439
|
+
def test_wildcard_root_index_routing_on_root
|
440
|
+
file_tree = {
|
441
|
+
'site': {
|
442
|
+
'index+.rb': '',
|
443
|
+
}
|
444
|
+
}
|
445
|
+
|
446
|
+
@root_dir = "/tmp/#{__FILE__.gsub('/', '-')}-#{SecureRandom.hex}"
|
447
|
+
make_tmp_file_tree(@root_dir, file_tree)
|
448
|
+
@rt = Syntropy::RoutingTree.new(root_dir: File.join(@root_dir, 'site'), mount_path: '/')
|
449
|
+
|
450
|
+
router = @rt.router_proc
|
451
|
+
|
452
|
+
route = router.('/', {})
|
453
|
+
assert_equal '/', route[:path]
|
454
|
+
|
455
|
+
route = router.('/foo', {})
|
456
|
+
assert_equal '/', route[:path]
|
457
|
+
|
458
|
+
route = router.('/foo/bar', {})
|
459
|
+
assert_equal '/', route[:path]
|
460
|
+
end
|
461
|
+
|
462
|
+
def test_wildcard_root_index_with_children
|
463
|
+
file_tree = {
|
464
|
+
'site': {
|
465
|
+
'about.rb': '',
|
466
|
+
'foo+.rb': '',
|
467
|
+
'index+.rb': '',
|
468
|
+
}
|
469
|
+
}
|
470
|
+
|
471
|
+
@root_dir = "/tmp/#{__FILE__.gsub('/', '-')}-#{SecureRandom.hex}"
|
472
|
+
make_tmp_file_tree(@root_dir, file_tree)
|
473
|
+
@rt = Syntropy::RoutingTree.new(root_dir: File.join(@root_dir, 'site'), mount_path: '/docs')
|
474
|
+
router = @rt.router_proc
|
475
|
+
|
476
|
+
route = router.('/docs', {})
|
477
|
+
assert_equal '/docs', route[:path]
|
478
|
+
|
479
|
+
route = router.('/docs/about', {})
|
480
|
+
assert_equal '/docs/about', route[:path]
|
481
|
+
|
482
|
+
route = router.('/docs/foo', {})
|
483
|
+
assert_equal '/docs/foo+', route[:path]
|
484
|
+
|
485
|
+
route = router.('/docs/foo/bar', {})
|
486
|
+
assert_equal '/docs/foo+', route[:path]
|
487
|
+
|
488
|
+
route = router.('/docs/baz', {})
|
489
|
+
assert_equal '/docs', route[:path]
|
490
|
+
|
491
|
+
route = router.('/docsa', {})
|
492
|
+
assert_nil route
|
493
|
+
end
|
494
|
+
|
495
|
+
def test_wildcard_root_index_with_static_children
|
496
|
+
file_tree = {
|
497
|
+
'site': {
|
498
|
+
'about.rb': '',
|
499
|
+
'foo.rb': '',
|
500
|
+
'index+.rb': '',
|
501
|
+
}
|
502
|
+
}
|
503
|
+
|
504
|
+
@root_dir = "/tmp/#{__FILE__.gsub('/', '-')}-#{SecureRandom.hex}"
|
505
|
+
make_tmp_file_tree(@root_dir, file_tree)
|
506
|
+
@rt = Syntropy::RoutingTree.new(root_dir: File.join(@root_dir, 'site'), mount_path: '/docs')
|
507
|
+
router = @rt.router_proc
|
508
|
+
|
509
|
+
route = router.('/docs', {})
|
510
|
+
assert_equal '/docs', route[:path]
|
511
|
+
|
512
|
+
route = router.('/docs/about', {})
|
513
|
+
assert_equal '/docs/about', route[:path]
|
514
|
+
|
515
|
+
route = router.('/docs/foo', {})
|
516
|
+
assert_equal '/docs/foo', route[:path]
|
517
|
+
|
518
|
+
route = router.('/docs/foo/bar', {})
|
519
|
+
assert_equal '/docs', route[:path]
|
520
|
+
|
521
|
+
route = router.('/docs/baz', {})
|
522
|
+
assert_equal '/docs', route[:path]
|
523
|
+
|
524
|
+
route = router.('/docsa', {})
|
525
|
+
assert_nil route
|
526
|
+
end
|
527
|
+
|
528
|
+
def test_wildcard_nested_index
|
529
|
+
file_tree = {
|
530
|
+
'site': {
|
531
|
+
'foo': {
|
532
|
+
'index+.rb': ''
|
533
|
+
},
|
534
|
+
'bar': {
|
535
|
+
'about.rb': '',
|
536
|
+
'foo.rb': '',
|
537
|
+
'index+.rb': '',
|
538
|
+
},
|
539
|
+
# 'baz+.rb': ''
|
540
|
+
}
|
541
|
+
}
|
542
|
+
|
543
|
+
@root_dir = "/tmp/#{__FILE__.gsub('/', '-')}-#{SecureRandom.hex}"
|
544
|
+
make_tmp_file_tree(@root_dir, file_tree)
|
545
|
+
@rt = Syntropy::RoutingTree.new(root_dir: File.join(@root_dir, 'site'), mount_path: '/docs')
|
546
|
+
router = @rt.router_proc
|
547
|
+
|
548
|
+
route = router.('/docs', {})
|
549
|
+
assert_nil route
|
550
|
+
|
551
|
+
route = router.('/docs/foo', {})
|
552
|
+
assert_equal '/docs/foo', route[:path]
|
553
|
+
|
554
|
+
route = router.('/docs/foo/bar', {})
|
555
|
+
assert_equal '/docs/foo', route[:path]
|
556
|
+
|
557
|
+
route = router.('/docs/foo/about', {})
|
558
|
+
assert_equal '/docs/foo', route[:path]
|
559
|
+
|
560
|
+
route = router.('/docs/bar', {})
|
561
|
+
assert_equal '/docs/bar', route[:path]
|
562
|
+
|
563
|
+
route = router.('/docs/bar/baz', {})
|
564
|
+
assert_equal '/docs/bar', route[:path]
|
565
|
+
|
566
|
+
route = router.('/docs/bar/about', {})
|
567
|
+
assert_equal '/docs/bar/about', route[:path]
|
568
|
+
|
569
|
+
route = router.('/docs/bars/about', {})
|
570
|
+
assert_nil route
|
571
|
+
end
|
572
|
+
|
573
|
+
|
574
|
+
end
|