mustermann 0.4.0 → 1.0.0.beta2
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 +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
|