mustermann 0.4.0 → 1.0.0.beta2
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +61 -46
- data/lib/mustermann.rb +3 -2
- data/lib/mustermann/ast/compiler.rb +2 -0
- data/lib/mustermann/ast/node.rb +4 -0
- data/lib/mustermann/ast/parser.rb +1 -22
- data/lib/mustermann/ast/pattern.rb +3 -3
- data/lib/mustermann/ast/transformer.rb +53 -4
- data/lib/mustermann/caster.rb +2 -10
- data/lib/mustermann/composite.rb +13 -3
- data/lib/mustermann/concat.rb +124 -0
- data/lib/mustermann/equality_map.rb +60 -0
- data/lib/mustermann/expander.rb +10 -6
- data/lib/mustermann/identity.rb +1 -0
- data/lib/mustermann/pattern.rb +53 -7
- data/lib/mustermann/regexp_based.rb +1 -1
- data/lib/mustermann/regular.rb +17 -2
- data/lib/mustermann/simple_match.rb +18 -5
- data/lib/mustermann/sinatra.rb +64 -16
- data/lib/mustermann/sinatra/parser.rb +45 -0
- data/lib/mustermann/sinatra/safe_renderer.rb +26 -0
- data/lib/mustermann/sinatra/try_convert.rb +48 -0
- data/lib/mustermann/version.rb +1 -1
- data/mustermann.gemspec +1 -2
- data/spec/composite_spec.rb +20 -5
- data/spec/concat_spec.rb +114 -0
- data/spec/equality_map_spec.rb +25 -0
- data/spec/mustermann_spec.rb +8 -5
- data/spec/regular_spec.rb +40 -0
- data/spec/sinatra_spec.rb +85 -5
- metadata +15 -42
- data/lib/mustermann/router.rb +0 -9
- data/lib/mustermann/router/rack.rb +0 -47
- data/lib/mustermann/router/simple.rb +0 -142
- data/spec/router/rack_spec.rb +0 -39
- data/spec/router/simple_spec.rb +0 -32
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 90d5c978f69743fdbee3eec3efc6bc6c05a83dfb
|
4
|
+
data.tar.gz: e777bda67af6ff598fc5a584ae786fa78eb68c4b
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 2b92c4b696adefa6c6c0ce16fc6a3ef7a3e85834e9579dbc8a7108fc535888da5dc907f39c087fac8abf5b26a387b81e85c7c45f161b691e40f9709e382df07b
|
7
|
+
data.tar.gz: fe32e08661344ce33c9066610e6d1591999fb62210597714e1f406b2161e4e517438d5c026fe801b84a262ec97cdbc76e471ecffae233275888aa4d1f6139814
|
data/README.md
CHANGED
@@ -27,7 +27,7 @@ pattern.params('/a/b.c') # => { "prefix" => "a", splat => ["b", "c"] }
|
|
27
27
|
|
28
28
|
* **[Pattern Types](#-pattern-types):** Mustermann supports a wide variety of different pattern types, making it compatible with a large variety of existing software.
|
29
29
|
* **[Fine Grained Control](#-available-options):** You can easily adjust matching behavior and add constraints to the placeholders and capture groups.
|
30
|
-
* **[Binary Operators](#-binary-operators):** Patterns can be combined into composite patterns using binary operators.
|
30
|
+
* **[Binary Operators](#-binary-operators) and [Concatenation](#-concatenation):** Patterns can be combined into composite patterns using binary operators.
|
31
31
|
* **[Regexp Look Alike](#-regexp-look-alike):** Mustermann patterns can be used as a replacement for regular expressions.
|
32
32
|
* **[Parameter Parsing](#-parameter-parsing):** Mustermann can parse matched parameters into a Sinatra-style "params" hash, including type casting.
|
33
33
|
* **[Peeking](#-peeking):** Lets you check if the beginning of a string matches a pattern.
|
@@ -42,7 +42,6 @@ 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
|
* **[Mapper](#-mapper):** A simple tool for mapping one string to another based on patterns.
|
45
|
-
* **[Routers](#-routers):** Model execution flow based on pattern matching. Comes with a simple Rack router.
|
46
45
|
* **[Sinatra Integration](#-sinatra-integration):** Mustermann can be used as a [Sinatra](http://www.sinatrarb.com/) extension. Sinatra 2.0 and beyond will use Mustermann by default.
|
47
46
|
|
48
47
|
<a name="-pattern-types"></a>
|
@@ -95,6 +94,22 @@ first ^ second === "/foo/bar" # => false
|
|
95
94
|
|
96
95
|
These resulting objects are fully functional pattern objects, allowing you to call methods like `params` or `to_proc` on them. Moreover, *or* patterns created solely from expandable patterns will also be expandable. The same logic also applies to generating templates from *or* patterns.
|
97
96
|
|
97
|
+
<a name="-concatenation"></a>
|
98
|
+
## Concatenation
|
99
|
+
|
100
|
+
Similar to [Binary Operators](#-binary-operators), two patterns can be concatenated using `+`.
|
101
|
+
|
102
|
+
``` ruby
|
103
|
+
require 'mustermann'
|
104
|
+
|
105
|
+
prefix = Mustermann.new("/:prefix")
|
106
|
+
about = prefix + "/about"
|
107
|
+
|
108
|
+
about.params("/main/about") # => {"prefix" => "main"}
|
109
|
+
```
|
110
|
+
|
111
|
+
Patterns of different types can be mixed. The availability of `to_templates` and `expand` depends on the patterns being concatenated.
|
112
|
+
|
98
113
|
<a name="-regexp-look-alike"></a>
|
99
114
|
## Regexp Look Alike
|
100
115
|
|
@@ -285,7 +300,40 @@ pattern.expand(:append, slug: "foo", value: "bar") # => "/foo?value=bar"
|
|
285
300
|
<a name="-generating-templates"></a>
|
286
301
|
## Generating Templates
|
287
302
|
|
288
|
-
|
303
|
+
You can generate a list of URI templates that correspond to a Mustermann pattern (it is a list rather than a single template, as most pattern types are significantly more expressive than URI templates).
|
304
|
+
|
305
|
+
This comes in quite handy since URI templates are not made for pattern matching. That way you can easily use a more precise template syntax and have it automatically generate hypermedia links for you.
|
306
|
+
|
307
|
+
Template generation is supported by almost all patterns (notable exceptions are `shell`, `regexp` and `simple` patterns).
|
308
|
+
|
309
|
+
``` ruby
|
310
|
+
require 'mustermann'
|
311
|
+
|
312
|
+
Mustermann.new("/:name").to_templates # => ["/{name}"]
|
313
|
+
Mustermann.new("/:foo(@:bar)?/*baz").to_templates # => ["/{foo}@{bar}/{+baz}", "/{foo}/{+baz}"]
|
314
|
+
Mustermann.new("/{name}", type: :template).to_templates # => ["/{name}"
|
315
|
+
```
|
316
|
+
|
317
|
+
Union Composite patterns (with the | operator) support template generation if all patterns they are composed of also support it.
|
318
|
+
|
319
|
+
``` ruby
|
320
|
+
require 'mustermann'
|
321
|
+
|
322
|
+
pattern = Mustermann.new('/:name')
|
323
|
+
pattern |= Mustermann.new('/{name}', type: :template)
|
324
|
+
pattern |= Mustermann.new('/example/*nested')
|
325
|
+
pattern.to_templates # => ["/{name}", "/example/{+nested}"]
|
326
|
+
```
|
327
|
+
|
328
|
+
If accepting arbitrary patterns, you can and should use `respond_to?` to check feature availability.
|
329
|
+
|
330
|
+
``` ruby
|
331
|
+
if pattern.respond_to? :to_templates
|
332
|
+
pattern.to_templates
|
333
|
+
else
|
334
|
+
warn "does not support template generation"
|
335
|
+
end
|
336
|
+
```
|
289
337
|
|
290
338
|
<a name="-proc-look-alike"></a>
|
291
339
|
## Proc Look Alike
|
@@ -326,47 +374,6 @@ mapper['/foo.xml'] # => "/foo/view.xml"
|
|
326
374
|
mapper['/foo/bar'] # => "/foo/bar"
|
327
375
|
```
|
328
376
|
|
329
|
-
<a name="-routers"></a>
|
330
|
-
## Routers
|
331
|
-
|
332
|
-
Mustermann comes with basic router implementations that will call certain callbacks depending on the input.
|
333
|
-
|
334
|
-
### Simple Router
|
335
|
-
|
336
|
-
The simple router chooses callbacks based on an input string.
|
337
|
-
|
338
|
-
``` ruby
|
339
|
-
require 'mustermann/router/simple'
|
340
|
-
|
341
|
-
router = Mustermann::Router::Simple.new(default: 42)
|
342
|
-
router.on(':name', capture: :digit) { |string| string.to_i }
|
343
|
-
router.call("23") # => 23
|
344
|
-
router.call("example") # => 42
|
345
|
-
```
|
346
|
-
|
347
|
-
### Rack Router
|
348
|
-
|
349
|
-
This is not a full replacement for Rails, Sinatra, Cuba, etc, as it only cares about path based routing.
|
350
|
-
|
351
|
-
``` ruby
|
352
|
-
require 'mustermann/router/rack'
|
353
|
-
|
354
|
-
router = Mustermann::Router::Rack.new do
|
355
|
-
on '/' do |env|
|
356
|
-
[200, {'Content-Type' => 'text/plain'}, ['Hello World!']]
|
357
|
-
end
|
358
|
-
|
359
|
-
on '/:name' do |env|
|
360
|
-
name = env['mustermann.params']['name']
|
361
|
-
[200, {'Content-Type' => 'text/plain'}, ["Hello #{name}!"]]
|
362
|
-
end
|
363
|
-
|
364
|
-
on '/something/*', call: SomeApp
|
365
|
-
end
|
366
|
-
|
367
|
-
# in a config.ru
|
368
|
-
run router
|
369
|
-
```
|
370
377
|
<a name="-sinatra-integration"></a>
|
371
378
|
## Sinatra Integration
|
372
379
|
|
@@ -747,7 +754,15 @@ This comes with a few trade-offs:
|
|
747
754
|
|
748
755
|
**Supported options:**
|
749
756
|
[`uri_decode`](#-available-options--uri_decode),
|
750
|
-
[`ignore_unknown_options`](#-available-options--ignore_unknown_options)
|
757
|
+
[`ignore_unknown_options`](#-available-options--ignore_unknown_options), `check_anchors`.
|
758
|
+
|
759
|
+
The pattern string (or actual Regexp instance) should not contain anchors (`^` outside of square brackets, `$`, `\A`, `\z`, or `\Z`).
|
760
|
+
Anchors will be injected where necessary by Mustermann.
|
761
|
+
|
762
|
+
By default, Mustermann will raise a `Mustermann::CompileError` if an anchor is encountered.
|
763
|
+
If you still want it to contain anchors at your own risk, set the `check_anchors` option to `false`.
|
764
|
+
|
765
|
+
Using anchors will break [peeking](#-peeking) and [concatenation](#-concatenation).
|
751
766
|
|
752
767
|
<table>
|
753
768
|
<thead>
|
@@ -811,7 +826,7 @@ This comes with a few trade-offs:
|
|
811
826
|
</td>
|
812
827
|
</tr>
|
813
828
|
<tr>
|
814
|
-
<td><
|
829
|
+
<td><i>expression</i><b>|</b><i>expression</i><b>|</b><i>...</i></td>
|
815
830
|
<td>
|
816
831
|
Will match anything matching the nested expressions. May contain any other syntax element, including captures.
|
817
832
|
</td>
|
data/lib/mustermann.rb
CHANGED
@@ -1,5 +1,6 @@
|
|
1
1
|
require 'mustermann/pattern'
|
2
2
|
require 'mustermann/composite'
|
3
|
+
require 'mustermann/concat'
|
3
4
|
require 'thread'
|
4
5
|
|
5
6
|
# Namespace and main entry point for the Mustermann library.
|
@@ -57,7 +58,7 @@ module Mustermann
|
|
57
58
|
# @raise (see Mustermann::Pattern.new)
|
58
59
|
# @raise [TypeError] if the passed object cannot be converted to a pattern
|
59
60
|
# @see file:README.md#Types_and_Options "Types and Options" in the README
|
60
|
-
def self.new(*input, type: DEFAULT_TYPE, **options)
|
61
|
+
def self.new(*input, type: DEFAULT_TYPE, operator: :|, **options)
|
61
62
|
type ||= DEFAULT_TYPE
|
62
63
|
input = input.first if input.size < 2
|
63
64
|
case input
|
@@ -65,7 +66,7 @@ module Mustermann
|
|
65
66
|
when Regexp then self[:regexp].new(input, **options)
|
66
67
|
when String then self[type].new(input, **options)
|
67
68
|
when Symbol then self[:sinatra].new(input.inspect, **options)
|
68
|
-
when Array then
|
69
|
+
when Array then input.map { |i| new(i, type: type, **options) }.inject(operator)
|
69
70
|
else
|
70
71
|
pattern = input.to_pattern(type: type, **options) if input.respond_to? :to_pattern
|
71
72
|
raise TypeError, "#{input.class} can't be coerced into Mustermann::Pattern" if pattern.nil?
|
@@ -84,6 +84,8 @@ module Mustermann
|
|
84
84
|
# @!visibility private
|
85
85
|
def translate(**options)
|
86
86
|
return super(**options) if explode or not options[:parametric]
|
87
|
+
# Remove this line after fixing broken compatibility between 2.1 and 2.2
|
88
|
+
options.delete(:parametric) if options.has_key?(:parametric)
|
87
89
|
parametric super(parametric: false, **options)
|
88
90
|
end
|
89
91
|
|
data/lib/mustermann/ast/node.rb
CHANGED
@@ -124,25 +124,8 @@ module Mustermann
|
|
124
124
|
# @return [String, MatchData, nil]
|
125
125
|
# @!visibility private
|
126
126
|
def scan(regexp)
|
127
|
-
match_buffer(:scan, regexp)
|
128
|
-
end
|
129
|
-
|
130
|
-
# Wrapper around {StringScanner#check} that turns strings into escaped
|
131
|
-
# regular expressions and returns a MatchData if the regexp has any
|
132
|
-
# named captures.
|
133
|
-
#
|
134
|
-
# @param [Regexp, String] regexp
|
135
|
-
# @see StringScanner#check
|
136
|
-
# @return [String, MatchData, nil]
|
137
|
-
# @!visibility private
|
138
|
-
def check(regexp)
|
139
|
-
match_buffer(:check, regexp)
|
140
|
-
end
|
141
|
-
|
142
|
-
# @!visibility private
|
143
|
-
def match_buffer(method, regexp)
|
144
127
|
regexp = Regexp.new(Regexp.escape(regexp)) unless regexp.is_a? Regexp
|
145
|
-
string = buffer.
|
128
|
+
string = buffer.scan(regexp)
|
146
129
|
regexp.names.any? ? regexp.match(string) : string
|
147
130
|
end
|
148
131
|
|
@@ -245,10 +228,6 @@ module Mustermann
|
|
245
228
|
char = "space" if char == " "
|
246
229
|
raise exception, "unexpected #{char || "end of string"} while parsing #{string.inspect}"
|
247
230
|
end
|
248
|
-
|
249
|
-
private :match_buffer
|
250
231
|
end
|
251
|
-
|
252
|
-
private_constant :Parser
|
253
232
|
end
|
254
233
|
end
|
@@ -7,7 +7,7 @@ require 'mustermann/ast/template_generator'
|
|
7
7
|
require 'mustermann/ast/param_scanner'
|
8
8
|
require 'mustermann/regexp_based'
|
9
9
|
require 'mustermann/expander'
|
10
|
-
require '
|
10
|
+
require 'mustermann/equality_map'
|
11
11
|
|
12
12
|
module Mustermann
|
13
13
|
# @see Mustermann::AST::Pattern
|
@@ -86,7 +86,7 @@ module Mustermann
|
|
86
86
|
# Internal AST representation of pattern.
|
87
87
|
# @!visibility private
|
88
88
|
def to_ast
|
89
|
-
@ast_cache ||=
|
89
|
+
@ast_cache ||= EqualityMap.new
|
90
90
|
@ast_cache.fetch(@string) do
|
91
91
|
ast = parse(@string, pattern: self)
|
92
92
|
ast &&= transform(ast)
|
@@ -133,4 +133,4 @@ module Mustermann
|
|
133
133
|
private :compile, :parse, :transform, :validate, :generate_templates, :param_converters, :scan_params, :set_boundaries
|
134
134
|
end
|
135
135
|
end
|
136
|
-
end
|
136
|
+
end
|
@@ -15,10 +15,59 @@ module Mustermann
|
|
15
15
|
new.translate(tree)
|
16
16
|
end
|
17
17
|
|
18
|
-
|
19
|
-
translate(:
|
20
|
-
|
21
|
-
|
18
|
+
# recursive descent
|
19
|
+
translate(:node) do
|
20
|
+
node.payload = t(payload)
|
21
|
+
node
|
22
|
+
end
|
23
|
+
|
24
|
+
# ignore unknown objects on the tree
|
25
|
+
translate(Object) { node }
|
26
|
+
|
27
|
+
# turn a group containing or nodes into a union
|
28
|
+
# @!visibility private
|
29
|
+
class GroupTransformer < NodeTranslator
|
30
|
+
register :group
|
31
|
+
|
32
|
+
# @!visibility private
|
33
|
+
def translate
|
34
|
+
payload.flatten! if payload.is_a?(Array)
|
35
|
+
return union if payload.any? { |e| e.is_a? :or }
|
36
|
+
self.payload = t(payload)
|
37
|
+
self
|
38
|
+
end
|
39
|
+
|
40
|
+
# @!visibility private
|
41
|
+
def union
|
42
|
+
groups = split_payload.map { |g| group(g) }
|
43
|
+
Node[:union].new(groups, start: node.start, stop: node.stop)
|
44
|
+
end
|
45
|
+
|
46
|
+
# @!visibility private
|
47
|
+
def group(elements)
|
48
|
+
return t(elements.first) if elements.size == 1
|
49
|
+
start, stop = elements.first.start, elements.last.stop if elements.any?
|
50
|
+
Node[:group].new(t(elements), start: start, stop: stop)
|
51
|
+
end
|
52
|
+
|
53
|
+
# @!visibility private
|
54
|
+
def split_payload
|
55
|
+
groups = [[]]
|
56
|
+
payload.each { |e| e.is_a?(:or) ? groups << [] : groups.last << e }
|
57
|
+
groups.map!
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
# inject a union node right inside the root node if it contains or nodes
|
62
|
+
# @!visibility private
|
63
|
+
class RootTransformer < GroupTransformer
|
64
|
+
register :root
|
65
|
+
|
66
|
+
# @!visibility private
|
67
|
+
def union
|
68
|
+
self.payload = [super]
|
69
|
+
self
|
70
|
+
end
|
22
71
|
end
|
23
72
|
|
24
73
|
# URI expression transformations depending on operator
|
data/lib/mustermann/caster.rb
CHANGED
@@ -22,6 +22,7 @@ module Mustermann
|
|
22
22
|
# @param [Array<Symbol, Regexp, #cast, #===>] types identifier for cast type (some need block)
|
23
23
|
# @!visibility private
|
24
24
|
def register(*types, &block)
|
25
|
+
return if types.empty? and block.nil?
|
25
26
|
types << Any.new(&block) if types.empty?
|
26
27
|
types.each { |type| self << caster_for(type, &block) }
|
27
28
|
end
|
@@ -41,6 +42,7 @@ module Mustermann
|
|
41
42
|
# @return [Hash] post-transform Hash
|
42
43
|
# @!visibility private
|
43
44
|
def cast(hash)
|
45
|
+
return hash if empty?
|
44
46
|
merge = {}
|
45
47
|
hash.delete_if do |key, value|
|
46
48
|
next unless casted = lazy.map { |e| e.cast(key, value) }.detect { |e| e }
|
@@ -50,16 +52,6 @@ module Mustermann
|
|
50
52
|
hash.update(merge)
|
51
53
|
end
|
52
54
|
|
53
|
-
# Specific cast for remove nil values.
|
54
|
-
# @!visibility private
|
55
|
-
module Nil
|
56
|
-
# @see Mustermann::Caster#cast
|
57
|
-
# @!visibility private
|
58
|
-
def self.cast(key, value)
|
59
|
-
{} if value.nil?
|
60
|
-
end
|
61
|
-
end
|
62
|
-
|
63
55
|
# Class for block based casts that are triggered for every key/value pair.
|
64
56
|
# @!visibility private
|
65
57
|
class Any
|
data/lib/mustermann/composite.rb
CHANGED
@@ -8,9 +8,9 @@ module Mustermann
|
|
8
8
|
supported_options :operator, :type
|
9
9
|
|
10
10
|
# @see Mustermann::Pattern.supported?
|
11
|
-
def self.supported?(option, **options)
|
11
|
+
def self.supported?(option, type: nil, **options)
|
12
12
|
return true if super
|
13
|
-
|
13
|
+
Mustermann[type || Mustermann::DEFAULT_TYPE].supported?(option, **options)
|
14
14
|
end
|
15
15
|
|
16
16
|
# @return [Mustermann::Pattern] a new composite pattern
|
@@ -33,6 +33,16 @@ module Mustermann
|
|
33
33
|
patterns == patterns_from(pattern)
|
34
34
|
end
|
35
35
|
|
36
|
+
# @see Mustermann::Pattern#eql?
|
37
|
+
def eql?(pattern)
|
38
|
+
patterns.eql? patterns_from(pattern)
|
39
|
+
end
|
40
|
+
|
41
|
+
# @see Mustermann::Pattern#hash
|
42
|
+
def hash
|
43
|
+
patterns.hash | operator.hash
|
44
|
+
end
|
45
|
+
|
36
46
|
# @see Mustermann::Pattern#===
|
37
47
|
def ===(string)
|
38
48
|
patterns.map { |p| p === string }.inject(operator)
|
@@ -61,7 +71,7 @@ module Mustermann
|
|
61
71
|
@expander.expand(behavior, values)
|
62
72
|
end
|
63
73
|
|
64
|
-
# (see Mustermann::Pattern#
|
74
|
+
# (see Mustermann::Pattern#to_templates)
|
65
75
|
def to_templates
|
66
76
|
raise NotImplementedError, 'template generation not supported' unless respond_to? :to_templates
|
67
77
|
patterns.flat_map(&:to_templates).uniq
|
@@ -0,0 +1,124 @@
|
|
1
|
+
module Mustermann
|
2
|
+
# Class for pattern objects that are a concatenation of other patterns.
|
3
|
+
# @see Mustermann::Pattern#+
|
4
|
+
class Concat < Composite
|
5
|
+
# Mixin for patterns to support native concatenation.
|
6
|
+
# @!visibility private
|
7
|
+
module Native
|
8
|
+
# @see Mustermann::Pattern#+
|
9
|
+
# @!visibility private
|
10
|
+
def +(other)
|
11
|
+
other &&= Mustermann.new(other, type: :identity, **options)
|
12
|
+
return super unless native = native_concat(other)
|
13
|
+
self.class.new(native, **options)
|
14
|
+
end
|
15
|
+
|
16
|
+
# @!visibility private
|
17
|
+
def native_concat(other)
|
18
|
+
"#{self}#{other}" if native_concat?(other)
|
19
|
+
end
|
20
|
+
|
21
|
+
# @!visibility private
|
22
|
+
def native_concat?(other)
|
23
|
+
other.class == self.class and other.options == options
|
24
|
+
end
|
25
|
+
|
26
|
+
private :native_concat, :native_concat?
|
27
|
+
end
|
28
|
+
|
29
|
+
# Should not be used directly.
|
30
|
+
# @!visibility private
|
31
|
+
def initialize(*)
|
32
|
+
super
|
33
|
+
AST::Validation.validate(combined_ast) if respond_to? :expand
|
34
|
+
end
|
35
|
+
|
36
|
+
# @see Mustermann::Composite#operator
|
37
|
+
# @return [Symbol] always :+
|
38
|
+
def operator
|
39
|
+
:+
|
40
|
+
end
|
41
|
+
|
42
|
+
# @see Mustermann::Pattern#===
|
43
|
+
def ===(string)
|
44
|
+
peek_size(string) == string.size
|
45
|
+
end
|
46
|
+
|
47
|
+
# @see Mustermann::Pattern#match
|
48
|
+
def match(string)
|
49
|
+
peeked = peek_match(string)
|
50
|
+
peeked if peeked.to_s == string
|
51
|
+
end
|
52
|
+
|
53
|
+
# @see Mustermann::Pattern#params
|
54
|
+
def params(string)
|
55
|
+
params, size = peek_params(string)
|
56
|
+
params if size == string.size
|
57
|
+
end
|
58
|
+
|
59
|
+
# @see Mustermann::Pattern#peek_size
|
60
|
+
def peek_size(string)
|
61
|
+
pump(string) { |p,s| p.peek_size(s) }
|
62
|
+
end
|
63
|
+
|
64
|
+
# @see Mustermann::Pattern#peek_match
|
65
|
+
def peek_match(string)
|
66
|
+
pump(string, initial: SimpleMatch.new) do |pattern, substring|
|
67
|
+
return unless match = pattern.peek_match(substring)
|
68
|
+
[match, match.to_s.size]
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
# @see Mustermann::Pattern#peek_params
|
73
|
+
def peek_params(string)
|
74
|
+
pump(string, inject_with: :merge, with_size: true) { |p, s| p.peek_params(s) }
|
75
|
+
end
|
76
|
+
|
77
|
+
# (see Mustermann::Pattern#expand)
|
78
|
+
def expand(behavior = nil, values = {})
|
79
|
+
raise NotImplementedError, 'expanding not supported' unless respond_to? :expand
|
80
|
+
@expander ||= Mustermann::Expander.new(self) { combined_ast }
|
81
|
+
@expander.expand(behavior, values)
|
82
|
+
end
|
83
|
+
|
84
|
+
# (see Mustermann::Pattern#to_templates)
|
85
|
+
def to_templates
|
86
|
+
raise NotImplementedError, 'template generation not supported' unless respond_to? :to_templates
|
87
|
+
@to_templates ||= patterns.inject(['']) { |list, pattern| list.product(pattern.to_templates).map(&:join) }.uniq
|
88
|
+
end
|
89
|
+
|
90
|
+
# @!visibility private
|
91
|
+
def respond_to_special?(method)
|
92
|
+
method = :to_ast if method.to_sym == :expand
|
93
|
+
patterns.all? { |p| p.respond_to?(method) }
|
94
|
+
end
|
95
|
+
|
96
|
+
# used to generate results for various methods by scanning through an input string
|
97
|
+
# @!visibility private
|
98
|
+
def pump(string, inject_with: :+, initial: nil, with_size: false)
|
99
|
+
substring = string
|
100
|
+
results = Array(initial)
|
101
|
+
|
102
|
+
patterns.each do |pattern|
|
103
|
+
result, size = yield(pattern, substring)
|
104
|
+
return unless result
|
105
|
+
results << result
|
106
|
+
size ||= result
|
107
|
+
substring = substring[size..-1]
|
108
|
+
end
|
109
|
+
|
110
|
+
results = results.inject(inject_with)
|
111
|
+
with_size ? [results, string.size - substring.size] : results
|
112
|
+
end
|
113
|
+
|
114
|
+
# generates one big AST from all patterns
|
115
|
+
# will not check if patterns support AST generation
|
116
|
+
# @!visibility private
|
117
|
+
def combined_ast
|
118
|
+
payload = patterns.map { |p| AST::Node[:group].new(p.to_ast.payload) }
|
119
|
+
AST::Node[:root].new(payload)
|
120
|
+
end
|
121
|
+
|
122
|
+
private :combined_ast, :pump
|
123
|
+
end
|
124
|
+
end
|