ratalada-contrib 3.1.0 → 3.1.3

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: bfdfa2c6c7ee4b7dc67028882e3de270a3d4b54c32d540fc89ba8e7e0ba50ab5
4
- data.tar.gz: ca771c956b1c2ee771f316e1e84d45d6d709154884d856059867fb7307693e38
3
+ metadata.gz: bd4a7fbadabdbb7170fe43f3a2c5b95c5c48f3ff3922f85c54fc00e0a4c9adc6
4
+ data.tar.gz: '0100897c369bb0855515dad4ec9b9c169dcf7a35430283cb51fd81ea3941fbf0'
5
5
  SHA512:
6
- metadata.gz: 7a78f852ced399d457e904e3d2e3bd2b7c2a70083339b05b22d136042fb24f083436765cfe49365c3036f70dab830b793d7573c04bc3187fbbff92f34b2ee602
7
- data.tar.gz: 6c6bdd43c7901c61198c1f83654af50e4a4dc100d6983659627f86fa61120545bbba63e13607e8dd7054e59d3dd0e656465c042b71eb530c9fdcb145cb50cceb
6
+ metadata.gz: 0eab446bf859b69de75f4c1ee0963eaff5751c1ab50adefbb0a2df43c959155222f635ffe99e98fac3c686c9008d41a613659212d77245b6922eb58989fff17e
7
+ data.tar.gz: a470f4628f5e8de7ff27fde72d1b03e0a83161ab213df976f2f60d6ab0cf82c364b8e2e2e1796ef054e21b1c3336ea34a9d9a3b3602283d8a4d8d078791ad094
@@ -12,12 +12,54 @@ module Ratalada
12
12
  # hangs off it, which is how a route file's own `get "/members"` lands
13
13
  # under the prefix its path spelled.
14
14
  module SinatraAdapter
15
+ # Expo spelling, kept all the way to the pattern. Sinatra's own
16
+ # syntax has no way to say `[user-id]`: `:user-id` reads as the
17
+ # capture `user` followed by the literal `-id`, so a hyphenated
18
+ # parameter silently routes nowhere. Mustermann::Expo is the pattern
19
+ # the file path already is.
20
+ SPELLING = {placeholder: "[%s]", catch_all: "[...%s]"}.freeze
21
+
22
+ def route_spelling = SPELLING
23
+
24
+ # ONE APP PER ROUTE FILE, NOT ONE FOR THE TREE. `use`, `set`, `before`,
25
+ # `error` and `helpers` are all class-level Sinatra DSL, so evaluating
26
+ # every file into a single Sinatra::Base makes a _layout.rb reach files
27
+ # it does not sit above: an app/(api)/_layout.rb with
28
+ # `before { content_type(:json) }` relabels every HTML page in the tree.
29
+ # A layout is meant to scope those to its own subtree, which it can only
30
+ # do if the files under it have a class of their own.
31
+ #
32
+ # So each route file (with the layouts above it) becomes its own
33
+ # subclass, and they are chained: a Sinatra app used as rack middleware
34
+ # forwards the request on when none of its own routes matched
35
+ # (Base#route_missing), so the first file that spells the path answers
36
+ # and the rest fall through. build_map already orders them
37
+ # most-specific-first.
38
+ #
39
+ # ponytail: one middleware hop per route file, walked per request.
40
+ # Collapse the leaves into one class if a fat tree ever shows up in a
41
+ # profile -- the isolation is the point, not the chain.
42
+ def mount_files(parent, map)
43
+ map.each do |prefix, paths|
44
+ child = Class.new(::Sinatra::Base)
45
+ child.route_prefix = prefix
46
+ paths.each { |path| child.class_eval(File.read(path), path, 1) }
47
+
48
+ parent.use(child)
49
+ end
50
+ end
51
+
15
52
  def route(verb, path, options = {}, &block)
16
53
  unless route_prefix.to_s.empty?
17
- path = FileBased.join(route_prefix, path)
54
+ path = Mustermann.new(FileBased.join(route_prefix, path), type: :expo)
18
55
  end
19
56
 
20
- super
57
+ super(
58
+ verb,
59
+ path,
60
+ options,
61
+ &block
62
+ )
21
63
  end
22
64
  end
23
65
  end
@@ -20,9 +20,27 @@ module Ratalada
20
20
  #
21
21
  # Server.run { Ratalada::Contrib::Router::FileBased.mount(self, "app") }
22
22
  def mount(app, directory)
23
- build_map(directory).each do |prefix, paths|
24
- app.route_prefix = prefix
25
- paths.each { |path| app.class_eval(File.read(path), path, 1) }
23
+ # The adapter says how its frontend spells a placeholder; the default
24
+ # is `:name`, which is what hanami and grape read.
25
+ if app.respond_to?(:route_spelling)
26
+ spelling = app.route_spelling
27
+ else
28
+ spelling = {}
29
+ end
30
+
31
+ map = build_map(directory, **spelling)
32
+
33
+ # An adapter that can give each route file its own class does, so a
34
+ # _layout.rb's `use`, `set`, `before` and `error` reach only the files
35
+ # below it. Without that the whole tree shares one class and a nested
36
+ # layout's filter fires for every route in the app.
37
+ if app.respond_to?(:mount_files)
38
+ app.mount_files(app, map)
39
+ else
40
+ map.each do |prefix, paths|
41
+ app.route_prefix = prefix
42
+ paths.each { |path| app.class_eval(File.read(path), path, 1) }
43
+ end
26
44
  end
27
45
  end
28
46
 
@@ -75,12 +93,18 @@ module Ratalada
75
93
  "#{prefix}#{route.delete_suffix("/")}".sub(/\A\z/, "/")
76
94
  end
77
95
 
96
+ # Least specific last. A segment that is PARTLY literal beats a bare
97
+ # capture: `[index].[diffType]` and `[index]` both match "7.diff", and
98
+ # the one that spells the dot is the one that means it -- so the
99
+ # patterns are anchored to a single capture rather than to the whole
100
+ # segment, which `[index].[diffType]` would also satisfy.
78
101
  def specificity(route)
79
102
  route.split("/").drop(1).map do |segment|
80
103
  case segment
81
- when "[...#{Mustermann::Expo::NOT_FOUND_CAPTURE}]" then 3
82
- when /\A\[\.\.\..+\]\z/ then 2
83
- when /\A\[.+\]\z/ then 1
104
+ when "[...#{Mustermann::Expo::NOT_FOUND_CAPTURE}]" then 4
105
+ when /\A\[\.\.\.[^\[\]]+\]\z/ then 3
106
+ when /\A\[[^\[\]]+\]\z/ then 2
107
+ when /\[.+\]/ then 1
84
108
  else 0
85
109
  end
86
110
  end
@@ -2,6 +2,6 @@
2
2
 
3
3
  module Ratalada
4
4
  module Contrib
5
- VERSION = "3.1.0"
5
+ VERSION = "3.1.3"
6
6
  end
7
7
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: ratalada-contrib
3
3
  version: !ruby/object:Gem::Version
4
- version: 3.1.0
4
+ version: 3.1.3
5
5
  platform: ruby
6
6
  authors:
7
7
  - Nathan K