mustermann 4.0.0.beta1 → 4.0.0
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/README.md +4 -4
- data/lib/mustermann/ast/fast_pattern.rb +16 -11
- data/lib/mustermann/ast/pattern.rb +2 -1
- data/lib/mustermann/composite.rb +25 -6
- data/lib/mustermann/concat.rb +2 -2
- data/lib/mustermann/expander.rb +17 -0
- data/lib/mustermann/match.rb +19 -0
- data/lib/mustermann/pattern.rb +10 -0
- data/lib/mustermann/rails.rb +14 -0
- data/lib/mustermann/regexp_based.rb +47 -7
- data/lib/mustermann/set.rb +37 -0
- data/lib/mustermann/sinatra/try_convert.rb +49 -11
- data/lib/mustermann/sinatra.rb +33 -11
- data/lib/mustermann/version.rb +1 -1
- 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: a1787d2b11e609844ac6a3085ca8943c1b94e9026b14f7efadded04f63f6d299
|
|
4
|
+
data.tar.gz: f8fb8748dbd82305d12b23cc4a227c210e3f748cd6e23fdb97b1126ea582c62d
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 5a429bddc03f3744c091f8b322c799f71276f35db5e190818ec57890012f52d4d3248c388b1db2997bd7d8c18bdb13fad58808e9961e1cd4accb832d8ddf9251
|
|
7
|
+
data.tar.gz: ac56fdcb2c30b07578ffa163af51a402190ec9ee75be89378347c2e9954caaadf942340ce15819f291bac4a1139d2e239fab4ff058df35c719777902f521620a
|
data/README.md
CHANGED
|
@@ -42,7 +42,7 @@ pattern.params('/a/b.c') # => { "prefix" => "a", splat => ["b", "c"] }
|
|
|
42
42
|
These features are included in the library, but not loaded by default
|
|
43
43
|
|
|
44
44
|
* **[Pattern Set](#-pattern-set):** A collection of patterns with associated values, designed for building routing tables that dispatch efficiently as the number of routes grows.
|
|
45
|
-
* **Mustermann::Router:** A very basic rack router built on top of `Mustermann::Set` for demonstration purposes. Simple and fast.
|
|
45
|
+
* **Mustermann::Router:** A very basic rack router built on top of `Mustermann::Set` for demonstration purposes or small-footprint routing in Rack middleware. Simple and fast.
|
|
46
46
|
|
|
47
47
|
<a name="-pattern-types"></a>
|
|
48
48
|
## Pattern Types
|
|
@@ -120,7 +120,7 @@ require 'mustermann'
|
|
|
120
120
|
|
|
121
121
|
pattern = Mustermann.new('/:page')
|
|
122
122
|
pattern.match('/') # => nil
|
|
123
|
-
pattern.match('/home') # => #<
|
|
123
|
+
pattern.match('/home') # => #<Mustermann::Match>
|
|
124
124
|
pattern =~ '/home' # => 0
|
|
125
125
|
pattern === '/home' # => true (this allows using it in case statements)
|
|
126
126
|
|
|
@@ -723,10 +723,10 @@ Semi-greedy behavior is not specific to dots, it works with all characters or st
|
|
|
723
723
|
|
|
724
724
|
``` ruby
|
|
725
725
|
pattern = Mustermann.new(':a.:b', greedy: true)
|
|
726
|
-
pattern.match('a.b.c.d') # => #<
|
|
726
|
+
pattern.match('a.b.c.d') # => #<Mustermann::Match>
|
|
727
727
|
|
|
728
728
|
pattern = Mustermann.new(':a.:b', greedy: false)
|
|
729
|
-
pattern.match('a.b.c.d') # => #<
|
|
729
|
+
pattern.match('a.b.c.d') # => #<Mustermann::Match>
|
|
730
730
|
```
|
|
731
731
|
|
|
732
732
|
<a name="-available-options--space_matches_plus"></a>
|
|
@@ -26,17 +26,6 @@ module Mustermann
|
|
|
26
26
|
|
|
27
27
|
private_constant :SIMPLE, :ENCODED, :SEGMENT_SCAN
|
|
28
28
|
|
|
29
|
-
# Bypasses the generic build_match overhead for simple patterns: uses
|
|
30
|
-
# MatchData#named_captures directly and avoids match.to_s / post_match /
|
|
31
|
-
# pre_match calls (all no-ops for \A…\Z anchored regexps).
|
|
32
|
-
def match(string)
|
|
33
|
-
return super unless @fast_match
|
|
34
|
-
return unless match = @regexp.match(string)
|
|
35
|
-
params = match.named_captures
|
|
36
|
-
params.transform_values! { |v| unescape(v) } if string.include?('%')
|
|
37
|
-
Match.new(self, match, params:)
|
|
38
|
-
end
|
|
39
|
-
|
|
40
29
|
# Public override: fast path for simple patterns, falls through to super otherwise.
|
|
41
30
|
# Must remain public to match AST::Pattern#to_ast visibility.
|
|
42
31
|
def to_ast
|
|
@@ -46,8 +35,24 @@ module Mustermann
|
|
|
46
35
|
ast
|
|
47
36
|
end
|
|
48
37
|
|
|
38
|
+
def params(string = nil)
|
|
39
|
+
return super unless @fast_match
|
|
40
|
+
return unless md = @regexp.match(string)
|
|
41
|
+
result = md.named_captures
|
|
42
|
+
result.transform_values! { |v| v.include?('%') ? unescape(v) : v } if string.include?('%')
|
|
43
|
+
result
|
|
44
|
+
end
|
|
45
|
+
|
|
49
46
|
private
|
|
50
47
|
|
|
48
|
+
def build_match(regexp, string)
|
|
49
|
+
return super unless @fast_match
|
|
50
|
+
return unless match = regexp.match(string)
|
|
51
|
+
params = match.named_captures
|
|
52
|
+
params.transform_values! { |v| v.include?('%') ? unescape(v) : v } if string.include?('%')
|
|
53
|
+
Match.new(self, match, params: params)
|
|
54
|
+
end
|
|
55
|
+
|
|
51
56
|
def simple_pattern?
|
|
52
57
|
options[:capture].nil? &&
|
|
53
58
|
options[:except].nil? &&
|
|
@@ -138,7 +138,8 @@ module Mustermann
|
|
|
138
138
|
# @see Mustermann::Pattern#map_param
|
|
139
139
|
def map_param(key, value)
|
|
140
140
|
return super unless param_converters.include? key
|
|
141
|
-
|
|
141
|
+
converted = super
|
|
142
|
+
converted.nil? ? converted : param_converters[key][converted]
|
|
142
143
|
end
|
|
143
144
|
|
|
144
145
|
# @!visibility private
|
data/lib/mustermann/composite.rb
CHANGED
|
@@ -29,6 +29,11 @@ module Mustermann
|
|
|
29
29
|
@patterns = patterns.flat_map { |p| patterns_from(p, **options) }
|
|
30
30
|
end
|
|
31
31
|
|
|
32
|
+
# @see Mustermann::Pattern#names
|
|
33
|
+
def names
|
|
34
|
+
@names ||= patterns.flat_map { |p| p.respond_to?(:names) ? p.names : [] }.uniq
|
|
35
|
+
end
|
|
36
|
+
|
|
32
37
|
# @see Mustermann::Pattern#==
|
|
33
38
|
def ==(pattern)
|
|
34
39
|
patterns == patterns_from(pattern)
|
|
@@ -79,19 +84,33 @@ module Mustermann
|
|
|
79
84
|
end
|
|
80
85
|
|
|
81
86
|
# @return [String] the string representation of the pattern
|
|
82
|
-
def to_s
|
|
83
|
-
simple_inspect
|
|
84
|
-
end
|
|
87
|
+
def to_s = inspect
|
|
85
88
|
|
|
86
89
|
# @!visibility private
|
|
87
90
|
def inspect
|
|
88
|
-
"
|
|
91
|
+
"(#{simple_inspect})"
|
|
89
92
|
end
|
|
90
93
|
|
|
91
94
|
# @!visibility private
|
|
92
95
|
def simple_inspect
|
|
93
|
-
|
|
94
|
-
|
|
96
|
+
patterns.map { |p| p.is_a?(Composite) ? p.inspect : p.simple_inspect }.join(" #{operator} ")
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
# @!visibility private
|
|
100
|
+
def pretty_print(q)
|
|
101
|
+
q.group(1, "(", ")") do
|
|
102
|
+
patterns.each_with_index do |pattern, index|
|
|
103
|
+
unless index == 0
|
|
104
|
+
q.text " #{operator}"
|
|
105
|
+
q.breakable " "
|
|
106
|
+
end
|
|
107
|
+
if pattern.is_a?(Composite)
|
|
108
|
+
q.pp pattern
|
|
109
|
+
else
|
|
110
|
+
q.text pattern.simple_inspect
|
|
111
|
+
end
|
|
112
|
+
end
|
|
113
|
+
end
|
|
95
114
|
end
|
|
96
115
|
|
|
97
116
|
# @!visibility private
|
data/lib/mustermann/concat.rb
CHANGED
|
@@ -14,8 +14,8 @@ module Mustermann
|
|
|
14
14
|
concat = (self + patterns.inject(:+))
|
|
15
15
|
concat + other.patterns.slice(patterns.length..-1).inject(:+)
|
|
16
16
|
else
|
|
17
|
-
|
|
18
|
-
self.class.new(native, **options)
|
|
17
|
+
native, opts = native_concat(other)
|
|
18
|
+
native ? self.class.new(native, **options, **opts.to_h) : super
|
|
19
19
|
end
|
|
20
20
|
end
|
|
21
21
|
|
data/lib/mustermann/expander.rb
CHANGED
|
@@ -159,6 +159,23 @@ module Mustermann
|
|
|
159
159
|
end
|
|
160
160
|
end
|
|
161
161
|
|
|
162
|
+
# @!visibility private
|
|
163
|
+
def inspect
|
|
164
|
+
return "#<#{self.class.name}>" if @patterns.empty?
|
|
165
|
+
"#<#{self.class.name}: #{@patterns.map { |p| p.to_s.inspect }.join(", ")}>"
|
|
166
|
+
end
|
|
167
|
+
|
|
168
|
+
# @!visibility private
|
|
169
|
+
def pretty_print(q)
|
|
170
|
+
q.text "#<#{self.class.name}"
|
|
171
|
+
q.group(1, "", ">") do
|
|
172
|
+
@patterns.each_with_index do |pattern, index|
|
|
173
|
+
q.breakable(index == 0 ? " " : ", ")
|
|
174
|
+
q.pp pattern.to_s
|
|
175
|
+
end
|
|
176
|
+
end
|
|
177
|
+
end
|
|
178
|
+
|
|
162
179
|
# @see Object#==
|
|
163
180
|
def ==(other)
|
|
164
181
|
return false unless other.class == self.class
|
data/lib/mustermann/match.rb
CHANGED
|
@@ -132,5 +132,24 @@ module Mustermann
|
|
|
132
132
|
|
|
133
133
|
alias == eql?
|
|
134
134
|
alias to_h params
|
|
135
|
+
|
|
136
|
+
# @!visibility private
|
|
137
|
+
def inspect
|
|
138
|
+
params_str = params.map { |k, v| " #{k}:#{v.inspect}" }.join
|
|
139
|
+
"#<#{self.class.name}: #{@matched.inspect}#{params_str}>"
|
|
140
|
+
end
|
|
141
|
+
|
|
142
|
+
# @!visibility private
|
|
143
|
+
def pretty_print(q)
|
|
144
|
+
q.group(1, "#<#{self.class.name}:", ">") do
|
|
145
|
+
q.breakable
|
|
146
|
+
q.pp @matched
|
|
147
|
+
params.each do |key, value|
|
|
148
|
+
q.breakable
|
|
149
|
+
q.text("#{key}:")
|
|
150
|
+
q.pp value
|
|
151
|
+
end
|
|
152
|
+
end
|
|
153
|
+
end
|
|
135
154
|
end
|
|
136
155
|
end
|
data/lib/mustermann/pattern.rb
CHANGED
|
@@ -90,6 +90,9 @@ module Mustermann
|
|
|
90
90
|
Match.new(self, string) if self === string
|
|
91
91
|
end
|
|
92
92
|
|
|
93
|
+
# @return [Array<String>] list of named captures in the pattern
|
|
94
|
+
def names = []
|
|
95
|
+
|
|
93
96
|
# @param [String] string The string to match against
|
|
94
97
|
# @return [Integer, nil] nil if pattern does not match the string, zero if it does.
|
|
95
98
|
# @see http://ruby-doc.org/core-2.0/Regexp.html#method-i-3D-7E Regexp#=~
|
|
@@ -339,6 +342,13 @@ module Mustermann
|
|
|
339
342
|
method(method).owner != Mustermann::Pattern
|
|
340
343
|
end
|
|
341
344
|
|
|
345
|
+
# @!visibility private
|
|
346
|
+
def pretty_print(q)
|
|
347
|
+
q.text "#<%p:" % self.class
|
|
348
|
+
q.pp(@string)
|
|
349
|
+
q.text ">"
|
|
350
|
+
end
|
|
351
|
+
|
|
342
352
|
# @!visibility private
|
|
343
353
|
def inspect
|
|
344
354
|
"#<%p:%p>" % [self.class, @string]
|
data/lib/mustermann/rails.rb
CHANGED
|
@@ -45,5 +45,19 @@ module Mustermann
|
|
|
45
45
|
|
|
46
46
|
# Rails 5.0 fixes |
|
|
47
47
|
version('5', '6', '7', '8') { on(?|) { |c| node(:or) }}
|
|
48
|
+
|
|
49
|
+
# (see Mustermann::Pattern#|)
|
|
50
|
+
def |(other) = combine(other, :|) { super }
|
|
51
|
+
|
|
52
|
+
# (see Mustermann::Pattern#+)
|
|
53
|
+
def +(other) = combine(other, :+) { super }
|
|
54
|
+
|
|
55
|
+
private
|
|
56
|
+
|
|
57
|
+
def combine(other, operator)
|
|
58
|
+
return yield unless hybrid = Mustermann[:hybrid].try_convert(self, **options)
|
|
59
|
+
native = hybrid.public_send(operator, other)
|
|
60
|
+
native.is_a?(Composite) ? yield : native
|
|
61
|
+
end
|
|
48
62
|
end
|
|
49
63
|
end
|
|
@@ -11,15 +11,37 @@ module Mustermann
|
|
|
11
11
|
attr_reader :regexp
|
|
12
12
|
alias_method :to_regexp, :regexp
|
|
13
13
|
|
|
14
|
+
# @api private
|
|
15
|
+
supported_options :cache
|
|
16
|
+
|
|
14
17
|
# @param (see Mustermann::Pattern#initialize)
|
|
15
18
|
# @return (see Mustermann::Pattern#initialize)
|
|
16
19
|
# @see (see Mustermann::Pattern#initialize)
|
|
17
20
|
def initialize(string, **options)
|
|
21
|
+
cache = options.delete(:cache) { true }
|
|
22
|
+
|
|
18
23
|
super
|
|
19
|
-
regexp
|
|
20
|
-
@peek_regexp
|
|
21
|
-
@regexp
|
|
24
|
+
regexp = compile(**options)
|
|
25
|
+
@peek_regexp = /\A#{regexp}/
|
|
26
|
+
@regexp = /\A#{regexp}\Z/
|
|
22
27
|
@simple_captures = @regexp.named_captures.none? { |name, positions| positions.size > 1 || always_array?(name) }
|
|
28
|
+
|
|
29
|
+
cache_class = ObjectSpace::WeakKeyMap if defined?(ObjectSpace::WeakKeyMap)
|
|
30
|
+
|
|
31
|
+
case cache
|
|
32
|
+
when true
|
|
33
|
+
@match_cache = cache_class&.new || false
|
|
34
|
+
@peek_cache = cache_class&.new || false
|
|
35
|
+
when false
|
|
36
|
+
@match_cache = false
|
|
37
|
+
@peek_cache = false
|
|
38
|
+
when Hash
|
|
39
|
+
@match_cache = cache[:match] || cache_class&.new || false
|
|
40
|
+
@peek_cache = cache[:peek] || cache_class&.new || false
|
|
41
|
+
else
|
|
42
|
+
@match_cache = cache.new
|
|
43
|
+
@peek_cache = cache.new
|
|
44
|
+
end
|
|
23
45
|
end
|
|
24
46
|
|
|
25
47
|
# @param (see Mustermann::Pattern#peek_size)
|
|
@@ -33,20 +55,38 @@ module Mustermann
|
|
|
33
55
|
# @param (see Mustermann::Pattern#peek_match)
|
|
34
56
|
# @return (see Mustermann::Pattern#peek_match)
|
|
35
57
|
# @see (see Mustermann::Pattern#peek_match)
|
|
36
|
-
def peek_match(string) =
|
|
58
|
+
def peek_match(string) = cache_match(@peek_cache, @peek_regexp, string)
|
|
37
59
|
|
|
38
60
|
# @param (see Mustermann::Pattern#match)
|
|
39
61
|
# @return (see Mustermann::Pattern#match)
|
|
40
62
|
# @see (see Mustermann::Pattern#match)
|
|
41
|
-
def match(string) =
|
|
63
|
+
def match(string) = cache_match(@match_cache, @regexp, string)
|
|
64
|
+
|
|
65
|
+
# Extracts params directly from the regexp without allocating a Match object or
|
|
66
|
+
# populating the match cache — significant GC savings when called in hot loops.
|
|
67
|
+
# @param (see Mustermann::Pattern#params)
|
|
68
|
+
# @return (see Mustermann::Pattern#params)
|
|
69
|
+
def params(string = nil)
|
|
70
|
+
return unless md = @regexp.match(string)
|
|
71
|
+
build_params(md)
|
|
72
|
+
end
|
|
42
73
|
|
|
43
74
|
extend Forwardable
|
|
44
75
|
def_delegators :regexp, :===, :=~, :names
|
|
45
76
|
|
|
46
77
|
private
|
|
47
78
|
|
|
48
|
-
def
|
|
49
|
-
|
|
79
|
+
def cache_match(cache, regexp, string)
|
|
80
|
+
if cache
|
|
81
|
+
return cache[string] if cache.key?(string)
|
|
82
|
+
cache[string] = build_match(regexp, string)
|
|
83
|
+
else
|
|
84
|
+
build_match(regexp, string)
|
|
85
|
+
end
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
def build_match(regexp, string)
|
|
89
|
+
return unless match = regexp.match(string)
|
|
50
90
|
Match.new(self, match, params: build_params(match))
|
|
51
91
|
end
|
|
52
92
|
|
data/lib/mustermann/set.rb
CHANGED
|
@@ -185,6 +185,11 @@ module Mustermann
|
|
|
185
185
|
# @return [self]
|
|
186
186
|
# @raise [ArgumentError] if the pattern is not AST-based, or if a reserved symbol is used as a value
|
|
187
187
|
def add(pattern, *values)
|
|
188
|
+
if pattern.is_a? Composite and pattern.operator == :|
|
|
189
|
+
pattern.patterns.each { |p| add(p, *values) }
|
|
190
|
+
return self
|
|
191
|
+
end
|
|
192
|
+
|
|
188
193
|
pattern = Mustermann.new(pattern, **options)
|
|
189
194
|
raise ArgumentError, "Non-AST patterns are not supported" unless pattern.respond_to? :to_ast
|
|
190
195
|
|
|
@@ -380,6 +385,38 @@ module Mustermann
|
|
|
380
385
|
# Runs trie optimizations pro-actively and explicitly rather than at match time.
|
|
381
386
|
def optimize! = @matcher&.optimize!
|
|
382
387
|
|
|
388
|
+
# @!visibility private
|
|
389
|
+
def inspect # :nodoc:
|
|
390
|
+
mapping = @mapping.map do |pattern, values|
|
|
391
|
+
if values.size > 1 or values.first.is_a? Array
|
|
392
|
+
"%p => %p" % [pattern.to_s, values]
|
|
393
|
+
elsif values.first != nil
|
|
394
|
+
"%p => %p" % [pattern.to_s, values.first]
|
|
395
|
+
else
|
|
396
|
+
"%p" % pattern.to_s
|
|
397
|
+
end
|
|
398
|
+
end
|
|
399
|
+
"#<#{self.class.name}: #{mapping.join(", ")}>"
|
|
400
|
+
end
|
|
401
|
+
|
|
402
|
+
# @!visibility private
|
|
403
|
+
def pretty_print(q) # :nodoc:
|
|
404
|
+
q.text "#<#{self.class.name}"
|
|
405
|
+
q.group(1, "", ">") do
|
|
406
|
+
@mapping.each_with_index do |(pattern, values), index|
|
|
407
|
+
q.breakable(index == 0 ? " " : ", ")
|
|
408
|
+
q.pp(pattern.to_s)
|
|
409
|
+
if values.size > 1 or values.first.is_a? Array
|
|
410
|
+
q.text " => "
|
|
411
|
+
q.pp(values)
|
|
412
|
+
elsif values.first != nil
|
|
413
|
+
q.text " => "
|
|
414
|
+
q.pp(values.first)
|
|
415
|
+
end
|
|
416
|
+
end
|
|
417
|
+
end
|
|
418
|
+
end
|
|
419
|
+
|
|
383
420
|
protected
|
|
384
421
|
|
|
385
422
|
attr_reader :mapping
|
|
@@ -6,24 +6,34 @@ module Mustermann
|
|
|
6
6
|
class TryConvert < AST::Translator
|
|
7
7
|
# @return [Mustermann::Sinatra, nil]
|
|
8
8
|
# @!visibility private
|
|
9
|
-
def self.convert(input, **options)
|
|
10
|
-
new(options).translate(input)
|
|
9
|
+
def self.convert(type, input, **options)
|
|
10
|
+
new(type, **options).translate(input)
|
|
11
11
|
end
|
|
12
12
|
|
|
13
|
+
# Reserved variable names.
|
|
14
|
+
# @!visibility private
|
|
15
|
+
attr_reader :names
|
|
16
|
+
|
|
13
17
|
# Expected options for the resulting pattern.
|
|
14
18
|
# @!visibility private
|
|
15
19
|
attr_reader :options
|
|
16
20
|
|
|
21
|
+
# Expected pattern type for the resulting pattern.
|
|
22
|
+
# @!visibility private
|
|
23
|
+
attr_reader :type
|
|
24
|
+
|
|
17
25
|
# @!visibility private
|
|
18
|
-
def initialize(options)
|
|
26
|
+
def initialize(type, names: [], **options)
|
|
27
|
+
@names = names
|
|
19
28
|
@options = options
|
|
29
|
+
@type = type
|
|
20
30
|
end
|
|
21
31
|
|
|
22
32
|
# @return [Mustermann::Sinatra]
|
|
23
33
|
# @!visibility private
|
|
24
|
-
def new(input, escape
|
|
34
|
+
def new(input, escape: false, **opts)
|
|
25
35
|
input = Mustermann::Sinatra.escape(input) if escape
|
|
26
|
-
|
|
36
|
+
type.new(input, **opts, **options, ignore_unknown_options: true)
|
|
27
37
|
end
|
|
28
38
|
|
|
29
39
|
# @return [true, false] whether or not expected pattern should have uri_decode option set
|
|
@@ -32,15 +42,43 @@ module Mustermann
|
|
|
32
42
|
options.fetch(:uri_decode, true)
|
|
33
43
|
end
|
|
34
44
|
|
|
35
|
-
|
|
36
|
-
|
|
45
|
+
# @return [true, false] whether or not the given options are compatible with the expected options
|
|
46
|
+
# @!visibility private
|
|
47
|
+
def compatible_options?(other_options)
|
|
48
|
+
other_options.all? do |key, value|
|
|
49
|
+
case key
|
|
50
|
+
when :capture then compatible_capture_option?(value)
|
|
51
|
+
else value == options[key]
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
end
|
|
37
55
|
|
|
38
|
-
|
|
39
|
-
|
|
56
|
+
# @return [true, false] whether or not the given capture option is compatible with the expected capture option
|
|
57
|
+
# @!visibility private
|
|
58
|
+
def compatible_capture_option?(capture)
|
|
59
|
+
return true if names.empty?
|
|
60
|
+
case capture
|
|
61
|
+
when Hash then capture.all? { |n, o| !names.include?(n.to_s) and compatible_capture_option?(o) }
|
|
62
|
+
when Array then capture.all? { |o| compatible_capture_option?(o) }
|
|
63
|
+
else true
|
|
64
|
+
end
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
translate(Object) { nil }
|
|
68
|
+
translate(String) { t.new(self, escape: true) }
|
|
69
|
+
translate(Identity) { t.new(self, escape: true) if uri_decode == t.uri_decode }
|
|
70
|
+
|
|
71
|
+
translate(Sinatra) do
|
|
72
|
+
if node.class == t.type and t.options == options
|
|
73
|
+
node
|
|
74
|
+
elsif t.compatible_options? options
|
|
75
|
+
t.new(to_s, **options)
|
|
76
|
+
end
|
|
77
|
+
end
|
|
40
78
|
|
|
41
79
|
translate AST::Pattern do
|
|
42
|
-
next unless
|
|
43
|
-
t.new(SafeRenderer.translate(to_ast)) rescue nil
|
|
80
|
+
next unless t.compatible_options? options
|
|
81
|
+
t.new(SafeRenderer.translate(to_ast), **options) rescue nil
|
|
44
82
|
end
|
|
45
83
|
end
|
|
46
84
|
|
data/lib/mustermann/sinatra.rb
CHANGED
|
@@ -37,13 +37,13 @@ module Mustermann
|
|
|
37
37
|
# @return [Mustermann::Sinatra, nil] the converted pattern, if possible
|
|
38
38
|
# @!visibility private
|
|
39
39
|
def self.try_convert(input, **options)
|
|
40
|
-
TryConvert.convert(input, **options)
|
|
40
|
+
TryConvert.convert(self, input, **options)
|
|
41
41
|
end
|
|
42
42
|
|
|
43
43
|
# Creates a pattern that matches any string matching either one of the patterns.
|
|
44
44
|
# If a string is supplied, it is treated as a fully escaped Sinatra pattern.
|
|
45
45
|
#
|
|
46
|
-
# If the other pattern is also a
|
|
46
|
+
# If the other pattern is also a Sinatra pattern, it might join the two to a third
|
|
47
47
|
# sinatra pattern instead of generating a composite for efficiency reasons.
|
|
48
48
|
#
|
|
49
49
|
# This only happens if the sinatra pattern behaves exactly the same as a composite
|
|
@@ -59,12 +59,12 @@ module Mustermann
|
|
|
59
59
|
# @return [Mustermann::Pattern] a composite pattern
|
|
60
60
|
# @see Mustermann::Pattern#|
|
|
61
61
|
def |(other)
|
|
62
|
-
return super unless converted =
|
|
63
|
-
return super
|
|
64
|
-
self.class.new(safe_string + "|" + converted.safe_string, **options)
|
|
62
|
+
return super unless converted = try_convert(other)
|
|
63
|
+
return super if converted.names.any? { |name| names.include?(name) }
|
|
64
|
+
self.class.new(safe_string + "|" + converted.safe_string, **converted.options)
|
|
65
65
|
end
|
|
66
66
|
|
|
67
|
-
# Generates a string
|
|
67
|
+
# Generates a string representation of the pattern that can safely be used for def interpolation
|
|
68
68
|
# without changing its semantics.
|
|
69
69
|
#
|
|
70
70
|
# @example
|
|
@@ -72,19 +72,41 @@ module Mustermann
|
|
|
72
72
|
# unsafe = Mustermann.new("/:name")
|
|
73
73
|
#
|
|
74
74
|
# Mustermann.new("#{unsafe}bar").params("/foobar") # => { "namebar" => "foobar" }
|
|
75
|
-
# Mustermann.new("#{unsafe.safe_string}bar").params("/foobar") # => { "name" => "
|
|
75
|
+
# Mustermann.new("#{unsafe.safe_string}bar").params("/foobar") # => { "name" => "foo" }
|
|
76
76
|
#
|
|
77
|
-
# @return [String] string
|
|
77
|
+
# @return [String] string representations of the pattern
|
|
78
78
|
def safe_string
|
|
79
79
|
@safe_string ||= SafeRenderer.translate(to_ast)
|
|
80
80
|
end
|
|
81
81
|
|
|
82
82
|
# @!visibility private
|
|
83
83
|
def native_concat(other)
|
|
84
|
-
return unless converted =
|
|
85
|
-
safe_string + converted.safe_string
|
|
84
|
+
return unless converted = try_convert(other)
|
|
85
|
+
[safe_string + converted.safe_string, converted.options]
|
|
86
86
|
end
|
|
87
87
|
|
|
88
|
-
private
|
|
88
|
+
# @!visibility private
|
|
89
|
+
def normalize_capture(pattern)
|
|
90
|
+
case capture = pattern.options[:capture]
|
|
91
|
+
when Hash then capture.slice(*pattern.names.map(&:to_sym))
|
|
92
|
+
when nil then {}
|
|
93
|
+
else pattern.names.to_h { |name| [name.to_sym, capture] }
|
|
94
|
+
end
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
# @!visibility private
|
|
98
|
+
def try_convert(other)
|
|
99
|
+
options = self.options
|
|
100
|
+
|
|
101
|
+
if other.is_a? Pattern and other.names.any?
|
|
102
|
+
capture = normalize_capture(other).merge(normalize_capture(self))
|
|
103
|
+
capture = nil if capture.empty?
|
|
104
|
+
options = options.merge(capture:)
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
self.class.try_convert(other, names:, **options)
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
private :native_concat, :normalize_capture, :try_convert
|
|
89
111
|
end
|
|
90
112
|
end
|
data/lib/mustermann/version.rb
CHANGED