ratalada-contrib 2.1.0 → 3.0.1
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/lib/ratalada/contrib/router/file_based/expo.rb +55 -0
- data/lib/ratalada/contrib/router/file_based/grape_adapter.rb +15 -36
- data/lib/ratalada/contrib/router/file_based/hanami_adapter.rb +8 -40
- data/lib/ratalada/contrib/router/file_based/sinatra_adapter.rb +16 -72
- data/lib/ratalada/contrib/router/file_based.rb +48 -76
- data/lib/ratalada/contrib/version.rb +1 -1
- metadata +18 -3
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: f94789e43bf70a8638e24126024e340669543cdb8126516faf70e4efb0ebb553
|
|
4
|
+
data.tar.gz: 1e428b4d5dae6ed2dfc9ef06cc28d24bceb982f7255295d3918717e99a431881
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 7dfe78d482fbd3486981c591e50875219c0f2c1865c1cf7147547021a7a66aaeb535c9e1edeb1d09d85619047791d68805641bb577d2d62ea98267cf740b1669
|
|
7
|
+
data.tar.gz: 4ca2093d272fc1e124d2c5f6697193206af316b53daf1113e57125b295877226250ecdefb2453a86013a62b8c108820e57d42a419e98e74ff38f2d99c7f5ce95
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "mustermann"
|
|
4
|
+
require "mustermann/ast/pattern"
|
|
5
|
+
|
|
6
|
+
module Mustermann
|
|
7
|
+
# Expo Router style pattern implementation.
|
|
8
|
+
#
|
|
9
|
+
# @example
|
|
10
|
+
# Mustermann.new('/[foo]', type: :expo) === '/bar' # => true
|
|
11
|
+
#
|
|
12
|
+
# @see Mustermann::Pattern
|
|
13
|
+
class Expo < AST::Pattern
|
|
14
|
+
register :expo
|
|
15
|
+
|
|
16
|
+
NOT_FOUND = "+not-found"
|
|
17
|
+
NOT_FOUND_CAPTURE = "unmatched"
|
|
18
|
+
|
|
19
|
+
# Turns an Expo Router file path into the route it serves: file extensions
|
|
20
|
+
# and the +api suffix are dropped, a _layout file stands for its directory,
|
|
21
|
+
# group segments are invisible and a trailing index is its directory.
|
|
22
|
+
# Mirrors getContextKey/stripInvisibleSegmentsFromPath in expo-router.
|
|
23
|
+
# @!visibility private
|
|
24
|
+
def self.route(string)
|
|
25
|
+
string = string.sub(%r{\A(?:\.\.?/|/)+}, "")
|
|
26
|
+
string = string.sub(/(\+api)?\.rb$/, "").sub(%r{/?_layout$}, "")
|
|
27
|
+
segments = (?/ + string.delete_prefix(?/)).split(?/, -1)
|
|
28
|
+
if segments.last == "index"
|
|
29
|
+
segments.pop
|
|
30
|
+
end
|
|
31
|
+
segments.reject! { |s| s =~ /\A\(.+\)\z/ }
|
|
32
|
+
if segments.last == NOT_FOUND
|
|
33
|
+
segments[-1] = "[...#{NOT_FOUND_CAPTURE}]"
|
|
34
|
+
end
|
|
35
|
+
segments.join(?/).then { |route| route.empty? ? ?/ : route }
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def initialize(string, **options)
|
|
39
|
+
super(Expo.route(string), **options)
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
on(nil, ?]) { |c| unexpected(c) }
|
|
43
|
+
|
|
44
|
+
on(?[) do |char|
|
|
45
|
+
name = expect(%r{[^\[\]/]+}, char: char)
|
|
46
|
+
expect(?])
|
|
47
|
+
|
|
48
|
+
if name.start_with?("...")
|
|
49
|
+
node(:named_splat, name[3..], constraint: ".+", convert: ->(e) { e.split(?/) })
|
|
50
|
+
else
|
|
51
|
+
node(:capture, name)
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
end
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
require "grape"
|
|
3
|
+
require "ratalada/grape"
|
|
4
4
|
|
|
5
5
|
require_relative "../file_based"
|
|
6
6
|
|
|
@@ -8,47 +8,26 @@ module Ratalada
|
|
|
8
8
|
module Contrib
|
|
9
9
|
module Router
|
|
10
10
|
module FileBased
|
|
11
|
-
#
|
|
12
|
-
#
|
|
13
|
-
# contents in place of the block — and Grape then re-hosts that built API
|
|
14
|
-
# under the prefix its path spelled:
|
|
15
|
-
#
|
|
16
|
-
# parent.mount(file_api => "/teams/:team/settings")
|
|
17
|
-
#
|
|
18
|
-
# `mount` is Grape's own (DSL::Routing#mount): it stores the mount path on
|
|
19
|
-
# the child's settings and replays its endpoints into the parent, so the
|
|
20
|
-
# file's `get "/members"` compiles as "/teams/:team/settings/members" with
|
|
21
|
-
# :team in params. Nothing about Grape's path handling is reimplemented.
|
|
11
|
+
# Teaches Grape `route_prefix`, the same way as Sinatra: the paths of
|
|
12
|
+
# every route defined while it is set hang off it.
|
|
22
13
|
module GrapeAdapter
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
def build(directory)
|
|
27
|
-
Class.new(::Grape::API).tap do |app|
|
|
28
|
-
FileBased.build_map(directory).each do |prefix, paths|
|
|
29
|
-
app.mount(build_file(paths) => mount_path(prefix))
|
|
30
|
-
end
|
|
14
|
+
def route(methods, paths = ["/"], route_options = {}, &block)
|
|
15
|
+
unless route_prefix.to_s.empty?
|
|
16
|
+
paths = Array(paths).map { |path| FileBased.join(route_prefix, path.to_s) }
|
|
31
17
|
end
|
|
32
|
-
end
|
|
33
|
-
|
|
34
|
-
# One route file and the layouts above it, built exactly like a
|
|
35
|
-
# Server.run block: the layouts' `helpers`, `before` blocks and `use`
|
|
36
|
-
# calls land in the same API as the file's own routes.
|
|
37
|
-
def build_file(paths)
|
|
38
|
-
Class.new(::Grape::API).tap do |api|
|
|
39
|
-
paths.each { |path| api.class_eval(File.read(path), path, 1) }
|
|
40
|
-
end
|
|
41
|
-
end
|
|
42
18
|
|
|
43
|
-
|
|
44
|
-
def mount_path(prefix)
|
|
45
|
-
case prefix
|
|
46
|
-
when "" then "/"
|
|
47
|
-
else prefix
|
|
48
|
-
end
|
|
19
|
+
super
|
|
49
20
|
end
|
|
50
21
|
end
|
|
51
22
|
end
|
|
52
23
|
end
|
|
53
24
|
end
|
|
54
25
|
end
|
|
26
|
+
|
|
27
|
+
class Grape::API::Instance # rubocop:disable Style/ClassAndModuleChildren
|
|
28
|
+
class << self
|
|
29
|
+
attr_accessor :route_prefix
|
|
30
|
+
|
|
31
|
+
prepend Ratalada::Contrib::Router::FileBased::GrapeAdapter
|
|
32
|
+
end
|
|
33
|
+
end
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
require "hanami
|
|
3
|
+
require "ratalada/hanami"
|
|
4
4
|
|
|
5
5
|
require_relative "../file_based"
|
|
6
6
|
|
|
@@ -8,49 +8,17 @@ module Ratalada
|
|
|
8
8
|
module Contrib
|
|
9
9
|
module Router
|
|
10
10
|
module FileBased
|
|
11
|
-
#
|
|
12
|
-
#
|
|
13
|
-
#
|
|
14
|
-
# instead is `scope`, the prefix its own router applies while routes are
|
|
15
|
-
# being defined (Hanami::Router#scope). So the file is evaluated inside
|
|
16
|
-
# the scope its path spells, which is the same result one step earlier:
|
|
17
|
-
#
|
|
18
|
-
# # app/teams/[team]/settings/index.rb
|
|
19
|
-
# get "/members" do
|
|
20
|
-
# "members of #{params[:team]}"
|
|
21
|
-
# end
|
|
22
|
-
#
|
|
23
|
-
# becomes "/teams/:team/settings/members". The file's blocks still run in
|
|
24
|
-
# the app's own Block::Context, so `params`, `halt` and any `helpers` are
|
|
25
|
-
# the ones the app defines.
|
|
11
|
+
# Teaches Hanami::API `route_prefix`. Hanami's router already carries a
|
|
12
|
+
# path prefix — it is what `scope` sets for the length of its block —
|
|
13
|
+
# so this is that, set until the next file.
|
|
26
14
|
module HanamiAdapter
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
# Returns the rack app: a Hanami::API subclass instance, as the Hanami
|
|
30
|
-
# frontend builds it, with every route file evaluated into it.
|
|
31
|
-
def build(directory)
|
|
32
|
-
app = Class.new(::Hanami::API)
|
|
33
|
-
|
|
34
|
-
FileBased.build_map(directory).each do |prefix, paths|
|
|
35
|
-
mount_route_file(app, prefix, paths)
|
|
36
|
-
end
|
|
37
|
-
|
|
38
|
-
app.new
|
|
39
|
-
end
|
|
40
|
-
|
|
41
|
-
# The layouts above the file are evaluated in the same scope, ahead of
|
|
42
|
-
# it, so what they add (`use`, extra routes) applies to that prefix.
|
|
43
|
-
def mount_route_file(app, prefix, paths)
|
|
44
|
-
sources = paths.map { |path| [File.read(path), path] }
|
|
45
|
-
|
|
46
|
-
# `scope` instance_evals its block on the router, so the file's own
|
|
47
|
-
# `get`/`post` calls are the router's, under the prefix.
|
|
48
|
-
app.router.scope(prefix) do
|
|
49
|
-
sources.each { |source, path| instance_eval(source, path, 1) }
|
|
50
|
-
end
|
|
15
|
+
def route_prefix=(prefix)
|
|
16
|
+
@router.instance_variable_set(:@path_prefix, ::Hanami::Router::Prefix.new("/").join(prefix.to_s))
|
|
51
17
|
end
|
|
52
18
|
end
|
|
53
19
|
end
|
|
54
20
|
end
|
|
55
21
|
end
|
|
56
22
|
end
|
|
23
|
+
|
|
24
|
+
Hanami::API.extend(Ratalada::Contrib::Router::FileBased::HanamiAdapter)
|
|
@@ -1,8 +1,6 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
require "
|
|
4
|
-
require "rack"
|
|
5
|
-
require "sinatra/base"
|
|
3
|
+
require "ratalada/sinatra"
|
|
6
4
|
|
|
7
5
|
require_relative "../file_based"
|
|
8
6
|
|
|
@@ -10,81 +8,27 @@ module Ratalada
|
|
|
10
8
|
module Contrib
|
|
11
9
|
module Router
|
|
12
10
|
module FileBased
|
|
13
|
-
#
|
|
14
|
-
#
|
|
15
|
-
#
|
|
16
|
-
# a file is ordinary Sinatra:
|
|
17
|
-
#
|
|
18
|
-
# # app/teams/[team]/settings/index.rb
|
|
19
|
-
# get "/members" do
|
|
20
|
-
# "members of #{params[:team]}"
|
|
21
|
-
# end
|
|
22
|
-
#
|
|
23
|
-
# Building it yields an app object whose compiled routes are Mustermann
|
|
24
|
-
# patterns, and those are what the prefix from FileBased gets prepended
|
|
25
|
-
# to — Mustermann concatenates patterns itself, so "/teams/:team" +
|
|
26
|
-
# "/members" stays one pattern with both captures.
|
|
27
|
-
#
|
|
28
|
-
# The rewritten apps are then chained: a Sinatra app used as rack
|
|
29
|
-
# middleware forwards the request onward when none of its own routes
|
|
30
|
-
# matched (Base#route_missing), so the first file that spells the path
|
|
31
|
-
# answers and the last one falls through to a plain 404.
|
|
11
|
+
# Teaches Sinatra `route_prefix`: every route defined while it is set
|
|
12
|
+
# hangs off it, which is how a route file's own `get "/members"` lands
|
|
13
|
+
# under the prefix its path spelled.
|
|
32
14
|
module SinatraAdapter
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
module_function
|
|
38
|
-
|
|
39
|
-
def build(directory)
|
|
40
|
-
apps = FileBased.build_map(directory).map do |prefix, paths|
|
|
41
|
-
prefix_routes(build_file(paths), prefix)
|
|
42
|
-
end
|
|
43
|
-
|
|
44
|
-
# ponytail: one Sinatra app (and its middleware stack) per file, walked
|
|
45
|
-
# in order per request. Collapse into one app class if a fat tree ever
|
|
46
|
-
# shows up in a profile.
|
|
47
|
-
::Rack::Builder.new.tap do |builder|
|
|
48
|
-
apps.each { |app| builder.use(app) }
|
|
49
|
-
builder.run(NOT_FOUND)
|
|
50
|
-
end.to_app
|
|
51
|
-
end
|
|
52
|
-
|
|
53
|
-
# One route file and the layouts above it, built exactly like a
|
|
54
|
-
# Server.run block: the layouts' `helpers`, `before` filters and `use`
|
|
55
|
-
# calls land in the same class as the file's own routes.
|
|
56
|
-
def build_file(paths)
|
|
57
|
-
Class.new(::Sinatra::Base).tap do |app|
|
|
58
|
-
paths.each { |path| app.class_eval(File.read(path), path, 1) }
|
|
59
|
-
end
|
|
60
|
-
end
|
|
61
|
-
|
|
62
|
-
# Rewrites every route the built app holds to sit under `prefix`.
|
|
63
|
-
def prefix_routes(app, prefix)
|
|
64
|
-
pattern = ::Mustermann.new(prefix, type: :sinatra)
|
|
65
|
-
|
|
66
|
-
app.routes.each_value do |signatures|
|
|
67
|
-
signatures.map! do |(route, conditions, wrapper)|
|
|
68
|
-
[nest(pattern, route), conditions, wrapper]
|
|
69
|
-
end
|
|
15
|
+
def route(verb, path, options = {}, &block)
|
|
16
|
+
unless route_prefix.to_s.empty?
|
|
17
|
+
path = FileBased.join(route_prefix, path)
|
|
70
18
|
end
|
|
71
19
|
|
|
72
|
-
|
|
73
|
-
end
|
|
74
|
-
|
|
75
|
-
# A file's own "/" is the prefix itself, not the prefix with a trailing
|
|
76
|
-
# slash, so it is the one pattern that replaces rather than extends —
|
|
77
|
-
# as is any route in a file that sits at the root, where the prefix is
|
|
78
|
-
# empty.
|
|
79
|
-
def nest(prefix, route)
|
|
80
|
-
case [prefix.to_s, route.to_s]
|
|
81
|
-
in ["", _] then route
|
|
82
|
-
in [_, "/"] then prefix
|
|
83
|
-
else prefix + route
|
|
84
|
-
end
|
|
20
|
+
super
|
|
85
21
|
end
|
|
86
22
|
end
|
|
87
23
|
end
|
|
88
24
|
end
|
|
89
25
|
end
|
|
90
26
|
end
|
|
27
|
+
|
|
28
|
+
class Sinatra::Base # rubocop:disable Style/ClassAndModuleChildren
|
|
29
|
+
set :route_prefix, ""
|
|
30
|
+
|
|
31
|
+
class << self
|
|
32
|
+
prepend Ratalada::Contrib::Router::FileBased::SinatraAdapter
|
|
33
|
+
end
|
|
34
|
+
end
|
|
@@ -1,66 +1,56 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
+
require "ratalada"
|
|
4
|
+
|
|
5
|
+
require_relative "file_based/expo"
|
|
6
|
+
|
|
3
7
|
module Ratalada
|
|
4
8
|
module Contrib
|
|
5
9
|
module Router
|
|
6
|
-
# Turns an expo-router style directory of route files into an ordered map
|
|
7
|
-
# of `prefix => path`. The prefix is what the file's own routes hang off:
|
|
8
|
-
# a file says `get "/members" do ... end` and the adapter mounts that under
|
|
9
|
-
# the prefix its path spells, so each adapter only has to decide how it
|
|
10
|
-
# loads a file and how it spells a placeholder.
|
|
11
|
-
#
|
|
12
|
-
# Ratalada::Contrib::Router::FileBased.build_map("app")
|
|
13
|
-
# # => { "" => "app/index.rb",
|
|
14
|
-
# # "/organizations/:slug" => "app/organizations/[slug]/index.rb", ... }
|
|
15
|
-
#
|
|
16
|
-
# FileBased.join("/organizations/:slug", "/users") # => "/organizations/:slug/users"
|
|
17
|
-
# FileBased.join("/organizations/:slug", "/") # => "/organizations/:slug"
|
|
18
|
-
# FileBased.join("", "/") # => "/"
|
|
19
|
-
#
|
|
20
|
-
# Conventions, all borrowed from expo-router:
|
|
21
|
-
#
|
|
22
|
-
# index.rb the directory itself app/teams/index.rb => /teams
|
|
23
|
-
# [slug].rb a single dynamic segment => /:slug
|
|
24
|
-
# [...rest].rb a catch-all => /*rest
|
|
25
|
-
# (group)/ grouping only, not a segment app/(marketing)/docs.rb => /docs
|
|
26
|
-
# +not-found.rb the fallback => /*
|
|
27
|
-
# _layout.rb not a route of its own: its contents are evaluated
|
|
28
|
-
# into every route file below it, outermost first, so
|
|
29
|
-
# a layout is whatever the file could have written for
|
|
30
|
-
# itself (helpers, filters, middleware, extra routes).
|
|
31
|
-
# Any other `_` file or directory is ignored outright.
|
|
32
|
-
#
|
|
33
|
-
# A map value is the list of files to evaluate, in order: the `_layout.rb`
|
|
34
|
-
# of each directory from the root down, then the route file itself.
|
|
35
|
-
#
|
|
36
|
-
# The map is ordered most specific first (static segments before dynamic,
|
|
37
|
-
# dynamic before catch-all, +not-found last), so an adapter can define the
|
|
38
|
-
# routes in iteration order under a first-match-wins router.
|
|
39
10
|
module FileBased
|
|
40
|
-
NOT_FOUND = "+not-found"
|
|
41
11
|
LAYOUT = "_layout.rb"
|
|
42
12
|
|
|
43
13
|
module_function
|
|
44
14
|
|
|
45
|
-
|
|
46
|
-
|
|
15
|
+
# Evaluates every route file in the directory into an app class, under
|
|
16
|
+
# the prefix its path spells. That class is the frontend's own, so this
|
|
17
|
+
# is what a Server.run block calls: the block is already being evaluated
|
|
18
|
+
# into it, and `self` is it. Requiring the frontend's adapter is what
|
|
19
|
+
# teaches the class `route_prefix`.
|
|
20
|
+
#
|
|
21
|
+
# Server.run { Ratalada::Contrib::Router::FileBased.mount(self, "app") }
|
|
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) }
|
|
26
|
+
end
|
|
27
|
+
end
|
|
47
28
|
|
|
48
|
-
|
|
49
|
-
|
|
29
|
+
# The same, as a whole app built by whichever Ratalada frontend is in
|
|
30
|
+
# use — for mounting a directory somewhere other than the server's own
|
|
31
|
+
# run block.
|
|
32
|
+
def build(directory)
|
|
33
|
+
Ratalada.frontend.build(proc { FileBased.mount(self, directory) })
|
|
34
|
+
end
|
|
50
35
|
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
36
|
+
# A hash of route => the files that serve it, ordered most specific
|
|
37
|
+
# first. Mustermann::Expo does the file path => route translation; all
|
|
38
|
+
# this adds is which files to evaluate and how the adapter spells a
|
|
39
|
+
# placeholder.
|
|
40
|
+
#
|
|
41
|
+
# {
|
|
42
|
+
# "/organisations/:id/edit" => ["./app/organisations/[id]/edit.rb"]
|
|
43
|
+
# }
|
|
44
|
+
def build_map(directory, placeholder: ":%s", catch_all: "*%s")
|
|
45
|
+
root = File.expand_path(directory)
|
|
55
46
|
|
|
56
|
-
|
|
57
|
-
.
|
|
58
|
-
.
|
|
47
|
+
Dir.glob("**/*.rb", base: root)
|
|
48
|
+
.reject { |relative| relative.split("/").any? { |segment| segment.start_with?("_") } }
|
|
49
|
+
.map { |relative| [Mustermann::Expo.route(relative), files(root, relative)] }
|
|
50
|
+
.sort_by { |(route, _paths)| [specificity(route), route] }
|
|
51
|
+
.to_h { |(route, paths)| [spell(route, placeholder, catch_all), paths] }
|
|
59
52
|
end
|
|
60
53
|
|
|
61
|
-
# The route file, with the `_layout.rb` of every directory above it in
|
|
62
|
-
# front — outermost first, so an inner layout is evaluated last and can
|
|
63
|
-
# override what an outer one set up.
|
|
64
54
|
def files(root, relative)
|
|
65
55
|
directories = relative.split("/")[0..-2]
|
|
66
56
|
|
|
@@ -68,45 +58,27 @@ module Ratalada
|
|
|
68
58
|
File.join(root, *directories[0..index], LAYOUT)
|
|
69
59
|
end
|
|
70
60
|
|
|
71
|
-
[File.join(root, LAYOUT), *layouts]
|
|
61
|
+
[File.join(root, LAYOUT), *layouts]
|
|
62
|
+
.select { |path| File.file?(path) }
|
|
72
63
|
.push(File.join(root, relative))
|
|
73
64
|
end
|
|
74
65
|
|
|
75
|
-
#
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
.split("/")
|
|
81
|
-
.drop(1)
|
|
82
|
-
.reject { |segment| segment.match?(/\A\(.*\)\z/) }
|
|
83
|
-
end
|
|
84
|
-
end
|
|
85
|
-
|
|
86
|
-
def pattern(segments, placeholder, catch_all)
|
|
87
|
-
spelled = segments.map do |segment|
|
|
88
|
-
case segment
|
|
89
|
-
when /\A\[\.\.\.(.+)\]\z/ then format(catch_all, Regexp.last_match(1))
|
|
90
|
-
when /\A\[(.+)\]\z/ then format(placeholder, Regexp.last_match(1))
|
|
91
|
-
when NOT_FOUND then format(catch_all, "unmatched")
|
|
92
|
-
else segment
|
|
93
|
-
end
|
|
66
|
+
# An expo route in the adapter's own spelling: "/teams/[team]" => "/teams/:team".
|
|
67
|
+
# The root route is "" — a prefix nothing hangs off, not a route of "/".
|
|
68
|
+
def spell(route, placeholder, catch_all)
|
|
69
|
+
route.delete_suffix("/").gsub(%r{\[(\.\.\.)?([^\[\]/]+)\]}) do
|
|
70
|
+
format(Regexp.last_match(1) ? catch_all : placeholder, Regexp.last_match(2))
|
|
94
71
|
end
|
|
95
|
-
|
|
96
|
-
spelled.map { |segment| "/#{segment}" }.join
|
|
97
72
|
end
|
|
98
73
|
|
|
99
|
-
# Hangs one of a file's own routes off the prefix its path spelled.
|
|
100
74
|
def join(prefix, route)
|
|
101
75
|
"#{prefix}#{route.delete_suffix("/")}".sub(/\A\z/, "/")
|
|
102
76
|
end
|
|
103
77
|
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
def specificity(segments)
|
|
107
|
-
segments.map do |segment|
|
|
78
|
+
def specificity(route)
|
|
79
|
+
route.split("/").drop(1).map do |segment|
|
|
108
80
|
case segment
|
|
109
|
-
when
|
|
81
|
+
when "[...#{Mustermann::Expo::NOT_FOUND_CAPTURE}]" then 3
|
|
110
82
|
when /\A\[\.\.\..+\]\z/ then 2
|
|
111
83
|
when /\A\[.+\]\z/ then 1
|
|
112
84
|
else 0
|
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:
|
|
4
|
+
version: 3.0.1
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Nathan K
|
|
@@ -15,14 +15,28 @@ dependencies:
|
|
|
15
15
|
requirements:
|
|
16
16
|
- - "~>"
|
|
17
17
|
- !ruby/object:Gem::Version
|
|
18
|
-
version: '
|
|
18
|
+
version: '3.0'
|
|
19
19
|
type: :runtime
|
|
20
20
|
prerelease: false
|
|
21
21
|
version_requirements: !ruby/object:Gem::Requirement
|
|
22
22
|
requirements:
|
|
23
23
|
- - "~>"
|
|
24
24
|
- !ruby/object:Gem::Version
|
|
25
|
-
version: '
|
|
25
|
+
version: '3.0'
|
|
26
|
+
- !ruby/object:Gem::Dependency
|
|
27
|
+
name: mustermann
|
|
28
|
+
requirement: !ruby/object:Gem::Requirement
|
|
29
|
+
requirements:
|
|
30
|
+
- - ">"
|
|
31
|
+
- !ruby/object:Gem::Version
|
|
32
|
+
version: '2'
|
|
33
|
+
type: :runtime
|
|
34
|
+
prerelease: false
|
|
35
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
36
|
+
requirements:
|
|
37
|
+
- - ">"
|
|
38
|
+
- !ruby/object:Gem::Version
|
|
39
|
+
version: '2'
|
|
26
40
|
description: |
|
|
27
41
|
Optional add-on modules for ratalada that do not belong in the core gem.
|
|
28
42
|
Require only the ones you use; each pulls its own framework (the
|
|
@@ -42,6 +56,7 @@ files:
|
|
|
42
56
|
- lib/ratalada/contrib/inertia/middleware.rb
|
|
43
57
|
- lib/ratalada/contrib/inertia/response.rb
|
|
44
58
|
- lib/ratalada/contrib/router/file_based.rb
|
|
59
|
+
- lib/ratalada/contrib/router/file_based/expo.rb
|
|
45
60
|
- lib/ratalada/contrib/router/file_based/grape_adapter.rb
|
|
46
61
|
- lib/ratalada/contrib/router/file_based/hanami_adapter.rb
|
|
47
62
|
- lib/ratalada/contrib/router/file_based/sinatra_adapter.rb
|