mustermann 0.0.1 → 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +2 -1
- data/.travis.yml +4 -3
- data/.yardopts +1 -0
- data/README.md +53 -10
- data/Rakefile +4 -1
- data/bench/capturing.rb +42 -9
- data/bench/template_vs_addressable.rb +3 -0
- data/internals.md +64 -0
- data/lib/mustermann.rb +14 -5
- data/lib/mustermann/ast/compiler.rb +150 -0
- data/lib/mustermann/ast/expander.rb +112 -0
- data/lib/mustermann/ast/node.rb +155 -0
- data/lib/mustermann/ast/parser.rb +136 -0
- data/lib/mustermann/ast/pattern.rb +89 -0
- data/lib/mustermann/ast/transformer.rb +121 -0
- data/lib/mustermann/ast/translator.rb +111 -0
- data/lib/mustermann/ast/tree_renderer.rb +29 -0
- data/lib/mustermann/ast/validation.rb +40 -0
- data/lib/mustermann/error.rb +4 -12
- data/lib/mustermann/extension.rb +3 -6
- data/lib/mustermann/identity.rb +4 -4
- data/lib/mustermann/pattern.rb +34 -5
- data/lib/mustermann/rails.rb +7 -16
- data/lib/mustermann/regexp_based.rb +4 -4
- data/lib/mustermann/shell.rb +4 -4
- data/lib/mustermann/simple.rb +1 -1
- data/lib/mustermann/simple_match.rb +2 -2
- data/lib/mustermann/sinatra.rb +10 -20
- data/lib/mustermann/template.rb +11 -104
- data/lib/mustermann/version.rb +1 -1
- data/mustermann.gemspec +1 -1
- data/spec/extension_spec.rb +143 -0
- data/spec/mustermann_spec.rb +41 -0
- data/spec/pattern_spec.rb +16 -6
- data/spec/rails_spec.rb +77 -9
- data/spec/sinatra_spec.rb +6 -0
- data/spec/support.rb +5 -78
- data/spec/support/coverage.rb +18 -0
- data/spec/support/env.rb +6 -0
- data/spec/support/expand_matcher.rb +27 -0
- data/spec/support/match_matcher.rb +39 -0
- data/spec/support/pattern.rb +28 -0
- metadata +43 -43
- data/.test_queue_stats +0 -0
- data/lib/mustermann/ast.rb +0 -403
- data/spec/ast_spec.rb +0 -8
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 9a623efd27ea171ba18818550616788aeca18ded
|
4
|
+
data.tar.gz: f8e7956944902e37495c44085662cb39cedea8a9
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: f8e52f5e109283a3f6d9d2823b1410e3e41e3d8c3d3787e767c70fa8e7a018ff53738f994d856947c2473f8ef94c65eb56de79f51132aa729ca26a4d582ada2f
|
7
|
+
data.tar.gz: f3ff266371da60a7da88f492ad2ee3a46389a1a4806c2e57886f07b7b702408388ffaf3ff18175d59c1b67fdfafb2eb5a07d496ce8219cdccc3ae5fd90f06203
|
data/.gitignore
CHANGED
data/.travis.yml
CHANGED
data/.yardopts
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
- README.md internals.md
|
data/README.md
CHANGED
@@ -1,10 +1,8 @@
|
|
1
|
-
# Mustermann
|
1
|
+
# The Amazing Mustermann
|
2
2
|
|
3
|
-
[![Build Status](https://travis-ci.org/rkh/mustermann.png?branch=master)](https://travis-ci.org/rkh/mustermann)
|
4
|
-
|
5
|
-
[
|
6
|
-
[![Dependency Status](https://gemnasium.com/rkh/mustermann.png)](https://gemnasium.com/rkh/mustermann)
|
7
|
-
[![Gem Version](https://badge.fury.io/rb/mustermann.png)](http://badge.fury.io/rb/mustermann)
|
3
|
+
[![Build Status](https://travis-ci.org/rkh/mustermann.png?branch=master)](https://travis-ci.org/rkh/mustermann) [![Coverage Status](https://coveralls.io/repos/rkh/mustermann/badge.png?branch=master)](https://coveralls.io/r/rkh/mustermann) [![Code Climate](https://codeclimate.com/github/rkh/mustermann.png)](https://codeclimate.com/github/rkh/mustermann) [![Dependency Status](https://gemnasium.com/rkh/mustermann.png)](https://gemnasium.com/rkh/mustermann) [![Gem Version](https://badge.fury.io/rb/mustermann.png)](http://badge.fury.io/rb/mustermann)
|
4
|
+
|
5
|
+
*Make sure you view the correct docs: [latest release](http://rubydoc.info/gems/mustermann/frames), [master](http://rubydoc.info/github/rkh/mustermann/master/frames).*
|
8
6
|
|
9
7
|
Welcome to [Mustermann](http://en.wikipedia.org/wiki/List_of_placeholder_names_by_language#German). Mustermann is your personal string matching expert. As an expert in the field of strings and patterns, Mustermann also has no runtime dependencies and is fully covered with specs and documentation.
|
10
8
|
|
@@ -29,14 +27,22 @@ pattern = Mustermann.new('/:prefix/*.*')
|
|
29
27
|
pattern.params('/a/b.c') # => { "prefix" => "a", splat => ["b", "c"] }
|
30
28
|
```
|
31
29
|
|
32
|
-
|
30
|
+
Similarly, it is also possible to generate a string from a pattern by expanding it with such a hash:
|
31
|
+
|
32
|
+
``` ruby
|
33
|
+
pattern = Mustermann.new('/:file(.:ext)?')
|
34
|
+
pattern.expand(file: 'pony') # => "/pony"
|
35
|
+
pattern.expand(file: 'pony', ext: 'jpg') # => "/pony.jpg"
|
36
|
+
```
|
37
|
+
|
38
|
+
It's generally a good idea to reuse pattern objects, since as much computation as possible is happening during object creation, so that the actual matching or expanding is quite fast.
|
33
39
|
|
34
40
|
## Types and Options
|
35
41
|
|
36
42
|
You can pass in additional options to take fine grained control over the pattern:
|
37
43
|
|
38
44
|
``` ruby
|
39
|
-
Mustermann.new('/:foo.:bar', capture: :alpha) # :foo and :bar will only match alphabetic
|
45
|
+
Mustermann.new('/:foo.:bar', capture: :alpha) # :foo and :bar will only match alphabetic characters
|
40
46
|
```
|
41
47
|
|
42
48
|
In fact, you can even completely change the pattern type:
|
@@ -639,6 +645,43 @@ you should set `uri_decode` to `false` in order to conform with the specificatio
|
|
639
645
|
|
640
646
|
If you are looking for an alternative implementation that also supports expanding, check out [addressable](http://addressable.rubyforge.org/).
|
641
647
|
|
642
|
-
##
|
648
|
+
## Requirements
|
649
|
+
|
650
|
+
Mustermann has no dependencies besides a Ruby 2.0 compatible Ruby implementation.
|
651
|
+
|
652
|
+
It is known to work on **MRI 2.0** and **MRI trunk**.
|
653
|
+
**JRuby** is not yet supported, but is likely to follow soon (see [issue #2](https://github.com/rkh/mustermann/issues/2) for up to date information on JRuby).
|
654
|
+
**Rubinius** is not yet able to parse the Mustermann source code.
|
655
|
+
|
656
|
+
## Release History
|
657
|
+
|
658
|
+
Mustermann follows [Semantic Versioning 2.0](http://semver.org/). Anything documented in the README or via YARD and not declared private is part of the public API.
|
659
|
+
|
660
|
+
### Stable Releases
|
661
|
+
|
662
|
+
There have been no stable releases yet. The code base is considered solid but I don't know of anyone using it in production yet.
|
663
|
+
As there has been no stable release yet, the API might still change, though I consider this unlikely.
|
664
|
+
|
665
|
+
### Development Releases
|
666
|
+
|
667
|
+
* **Mustermann 0.0.1** (2013-04-27)
|
668
|
+
* More Infos:
|
669
|
+
[RubyGems.org](http://rubygems.org/gems/mustermann/versions/0.0.1),
|
670
|
+
[RubyDoc.info](rubydoc.info/gems/mustermann/0.0.1/frames),
|
671
|
+
[GitHub.com](https://github.com/rkh/mustermann/tree/v0.0.1)
|
672
|
+
* Initial Release.
|
673
|
+
* **Mustermann 0.1.0** (2013-05-12)
|
674
|
+
* Add `Pattern#expand` for generating strings from patterns.
|
675
|
+
* Add better internal API for working with the AST.
|
676
|
+
* Improved documentation.
|
677
|
+
* Avoids parsing the path twice when used as Sinatra extension.
|
678
|
+
* Better exceptions for unknown pattern types.
|
679
|
+
* Better handling of edge cases around extend.
|
680
|
+
* More specs to ensure API stability.
|
681
|
+
* Largely rework internals of Sinatra, Rails and Template patterns.
|
682
|
+
|
683
|
+
### Upcoming Releases
|
643
684
|
|
644
|
-
Mustermann
|
685
|
+
* **Mustermann 0.2.0** (next release with new features)
|
686
|
+
* **Mustermann 1.0.0** (before Sinatra 2.0)
|
687
|
+
* First stable release.
|
data/Rakefile
CHANGED
data/bench/capturing.rb
CHANGED
@@ -2,23 +2,56 @@ $:.unshift File.expand_path('../lib', __dir__)
|
|
2
2
|
|
3
3
|
require 'benchmark'
|
4
4
|
require 'mustermann'
|
5
|
+
require 'mustermann/regexp_based'
|
5
6
|
require 'addressable/template'
|
6
7
|
|
8
|
+
|
9
|
+
Mustermann.register(:regexp, Class.new(Mustermann::RegexpBased) {
|
10
|
+
def compile(string, **options)
|
11
|
+
Regexp.new(string)
|
12
|
+
end
|
13
|
+
}, load: false)
|
14
|
+
|
15
|
+
Mustermann.register(:addressable, Class.new(Mustermann::RegexpBased) {
|
16
|
+
def compile(string, **options)
|
17
|
+
Addressable::Template.new(string)
|
18
|
+
end
|
19
|
+
}, load: false)
|
20
|
+
|
7
21
|
list = [
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
22
|
+
[:sinatra, '/*/:name' ],
|
23
|
+
[:rails, '/*prefix/:name' ],
|
24
|
+
[:simple, '/*/:name' ],
|
25
|
+
[:template, '{/prefix*}/{name}' ],
|
26
|
+
[:regexp, '\A\/(?<splat>.*?)\/(?<name>[^\/\?#]+)\Z' ],
|
27
|
+
[:addressable, '{/prefix*}/{name}' ]
|
14
28
|
]
|
15
29
|
|
30
|
+
def self.assert(value)
|
31
|
+
fail unless value
|
32
|
+
end
|
33
|
+
|
16
34
|
string = '/a/b/c/d'
|
35
|
+
name = 'd'
|
36
|
+
|
37
|
+
GC.disable
|
38
|
+
|
39
|
+
puts "Compilation:"
|
40
|
+
Benchmark.bmbm do |x|
|
41
|
+
list.each do |type, pattern|
|
42
|
+
x.report(type) { 1_000.times { Mustermann.new(pattern, type: type) } }
|
43
|
+
end
|
44
|
+
end
|
17
45
|
|
46
|
+
puts "", "Matching with two captures (one splat, one normal):"
|
18
47
|
Benchmark.bmbm do |x|
|
19
|
-
list.each do |pattern|
|
20
|
-
|
21
|
-
|
48
|
+
list.each do |type, pattern|
|
49
|
+
pattern = Mustermann.new(pattern, type: type)
|
50
|
+
x.report type do
|
51
|
+
10_000.times do
|
52
|
+
match = pattern.match(string)
|
53
|
+
assert match[:name] == name
|
54
|
+
end
|
22
55
|
end
|
23
56
|
end
|
24
57
|
end
|
@@ -18,6 +18,9 @@ require 'addressable/template'
|
|
18
18
|
explode = klass.new("{/segments*}")
|
19
19
|
x.report("explode, match") { 1_000.times { explode.match("/a/b/c").captures } }
|
20
20
|
x.report("explode, miss") { 1_000.times { explode.match("/a/b/c.miss") } }
|
21
|
+
|
22
|
+
expand = klass.new("/prefix/{foo}/something/{bar}")
|
23
|
+
x.report("expand") { 100.times { expand.expand(foo: 'foo', bar: 'bar').to_s } }
|
21
24
|
end
|
22
25
|
puts
|
23
26
|
end
|
data/internals.md
ADDED
@@ -0,0 +1,64 @@
|
|
1
|
+
# Internal API
|
2
|
+
|
3
|
+
This document describes how to use [Mustermann](README.md)'s internal API.
|
4
|
+
|
5
|
+
It is a secondary goal to keep the internal API as stable as possible, in a state where it would well be possible to interface with it.
|
6
|
+
However, the internal API is not covered by Semantic Versioning. As a rule of thumb, no backwards incompatible changes should be introduced to the API in minor releases (starting from 1.0.0).
|
7
|
+
|
8
|
+
Should the internal API gain widespread/production use, we might consider moving parts of it over into the public API.
|
9
|
+
|
10
|
+
Here is a quick example of what you can do with this:
|
11
|
+
|
12
|
+
``` ruby
|
13
|
+
require 'mustermann/ast/pattern'
|
14
|
+
|
15
|
+
class MyPattern < Mustermann::AST::Pattern
|
16
|
+
on("~") { |c| node(:capture, buffer[1]) if expect(/\{(\w+)\}/) }
|
17
|
+
on("+") { |c| node(:named_splat, buffer[1]) if expect(/\{(\w+)\}/) }
|
18
|
+
on("?") { |c| node(:optional, node(:capture, buffer[1])) if expect(/\{(\w+)\}/) }
|
19
|
+
end
|
20
|
+
|
21
|
+
pattern = MyPattern.new("/+{prefix}/~{page}/?{optional}")
|
22
|
+
pattern.params("/a/") # => nil
|
23
|
+
pattern.params("/a/b/") # => { "prefix" => "a", "page" => "b", "optional" => nil }
|
24
|
+
pattern.params("/a/b/c") # => { "prefix" => "a", "page" => "b", "optional" => "c" }
|
25
|
+
pattern.params("/a/b/c/") # => { "prefix" => "a/b", "page" => "c", "optional" => nil }
|
26
|
+
|
27
|
+
pattern.expand(prefix: "a", page: "foo") # => "/a/foo/"
|
28
|
+
pattern.expand(prefix: "a/b", page: "c/d") # => "/a/b/c%2Fd/"
|
29
|
+
|
30
|
+
require 'mustermann'
|
31
|
+
Mustermann.register(:my_pattern, MyPattern, load: false)
|
32
|
+
Mustermann.new('/+{prefix}/~{page}/?{optional}', type: :my_pattern) # => #<MyPattern:"/+{prefix}/~{page}/?{optional}">
|
33
|
+
|
34
|
+
require 'sinatra/base'
|
35
|
+
class MyApp < Sinatra::Base
|
36
|
+
register Mustermann
|
37
|
+
set :pattern, type: :my_pattern
|
38
|
+
|
39
|
+
get '/hello/~{name}' do
|
40
|
+
"Hello #{params[:name].capitalize}!"
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
require 'mustermann/ast/tree_renderer'
|
45
|
+
ast = MyPattern::Parser.parse(pattern.to_s)
|
46
|
+
puts Mustermann::AST::TreeRenderer.render(ast)
|
47
|
+
|
48
|
+
```
|
49
|
+
|
50
|
+
## Pattern Registration
|
51
|
+
|
52
|
+
...
|
53
|
+
|
54
|
+
## Build Your Own Pattern
|
55
|
+
|
56
|
+
...
|
57
|
+
|
58
|
+
## Patterns Based on Regular Expressions
|
59
|
+
|
60
|
+
...
|
61
|
+
|
62
|
+
## AST-Based Patterns
|
63
|
+
|
64
|
+
...
|
data/lib/mustermann.rb
CHANGED
@@ -1,18 +1,27 @@
|
|
1
1
|
# Namespace and main entry point for the Mustermann library.
|
2
2
|
#
|
3
|
-
# Under normal
|
3
|
+
# Under normal circumstances the only external API entry point you should be using is {Mustermann.new}.
|
4
4
|
module Mustermann
|
5
|
-
# @param [String] string The string
|
5
|
+
# @param [String] string The string representation of the new pattern
|
6
6
|
# @param [Hash] options The options hash
|
7
7
|
# @return [Mustermann::Pattern] pattern corresponding to string.
|
8
|
+
# @raise (see [])
|
9
|
+
# @raise (see Mustermann::Pattern.new)
|
8
10
|
# @see file:README.md#Types_and_Options "Types and Options" in the README
|
9
11
|
def self.new(string, type: :sinatra, **options)
|
10
12
|
options.any? ? self[type].new(string, **options) : self[type].new(string)
|
11
13
|
end
|
12
14
|
|
13
|
-
#
|
15
|
+
# Maps a type to its factory.
|
16
|
+
#
|
17
|
+
# @example
|
18
|
+
# Mustermann[:sinatra] # => Mustermann::Sinatra
|
19
|
+
#
|
20
|
+
# @param [Symbol] key a pattern type identifier
|
21
|
+
# @raise [ArgumentError] if the type is not supported
|
22
|
+
# @return [Class, #new] pattern factory
|
14
23
|
def self.[](key)
|
15
|
-
constant, library = register.fetch(key)
|
24
|
+
constant, library = register.fetch(key) { raise ArgumentError, "unsupported type %p" % key }
|
16
25
|
require library if library
|
17
26
|
constant.respond_to?(:new) ? constant : register[key] = const_get(constant)
|
18
27
|
end
|
@@ -26,7 +35,7 @@ module Mustermann
|
|
26
35
|
|
27
36
|
# @!visibility private
|
28
37
|
def self.extend_object(object)
|
29
|
-
return super unless defined? ::Sinatra::Base and object < ::Sinatra::Base
|
38
|
+
return super unless defined? ::Sinatra::Base and object.is_a? Class and object < ::Sinatra::Base
|
30
39
|
require 'mustermann/extension'
|
31
40
|
object.register Extension
|
32
41
|
end
|
@@ -0,0 +1,150 @@
|
|
1
|
+
require 'mustermann/ast/translator'
|
2
|
+
|
3
|
+
module Mustermann
|
4
|
+
# @see Mustermann::AST::Pattern
|
5
|
+
module AST
|
6
|
+
# Regexp compilation logic.
|
7
|
+
# @!visibility private
|
8
|
+
class Compiler < Translator
|
9
|
+
raises CompileError
|
10
|
+
|
11
|
+
# Trivial compilations
|
12
|
+
translate(Array) { |**o| map { |e| t(e, **o) }.join }
|
13
|
+
translate(:node) { |**o| t(payload, **o) }
|
14
|
+
translate(:separator) { |**o| Regexp.escape(payload) }
|
15
|
+
translate(:optional) { |**o| "(?:%s)?" % t(payload, **o) }
|
16
|
+
translate(:char) { |**o| t.encoded(payload, **o) }
|
17
|
+
|
18
|
+
translate :expression do |greedy: true, **options|
|
19
|
+
t(payload, allow_reserved: operator.allow_reserved, greedy: greedy && !operator.allow_reserved,
|
20
|
+
parametric: operator.parametric, separator: operator.separator, **options)
|
21
|
+
end
|
22
|
+
|
23
|
+
translate :with_look_ahead do |**options|
|
24
|
+
lookahead = each_leaf.inject("") do |ahead, element|
|
25
|
+
ahead + t(element, skip_optional: true, lookahead: ahead, greedy: false, no_captures: true, **options).to_s
|
26
|
+
end
|
27
|
+
lookahead << (at_end ? '$' : '/')
|
28
|
+
t(head, lookahead: lookahead, **options) + t(payload, **options)
|
29
|
+
end
|
30
|
+
|
31
|
+
# Capture compilation is complex. :(
|
32
|
+
# @!visibility private
|
33
|
+
class Capture < NodeTranslator
|
34
|
+
register :capture
|
35
|
+
|
36
|
+
# @!visibility private
|
37
|
+
def translate(**options)
|
38
|
+
return pattern(options) if options[:no_captures]
|
39
|
+
"(?<#{name}>#{translate(no_captures: true, **options)})"
|
40
|
+
end
|
41
|
+
|
42
|
+
# @return [String] regexp without the named capture
|
43
|
+
# @!visibility private
|
44
|
+
def pattern(capture: nil, **options)
|
45
|
+
case capture
|
46
|
+
when Symbol then from_symbol(capture, **options)
|
47
|
+
when Array then from_array(capture, **options)
|
48
|
+
when Hash then from_hash(capture, **options)
|
49
|
+
when String then from_string(capture, **options)
|
50
|
+
when nil then from_nil(**options)
|
51
|
+
else capture
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
private
|
56
|
+
def qualified(string, greedy: true, **options) "#{string}+#{?? unless greedy}" end
|
57
|
+
def with_lookahead(string, lookahead: nil, **options) lookahead ? "(?:(?!#{lookahead})#{string})" : string end
|
58
|
+
def from_hash(hash, **options) pattern(capture: hash[name.to_sym], **options) end
|
59
|
+
def from_array(array, **options) Regexp.union(*array.map { |e| pattern(capture: e, **options) }) end
|
60
|
+
def from_symbol(symbol, **options) qualified(with_lookahead("[[:#{symbol}:]]", **options), **options) end
|
61
|
+
def from_string(string, **options) Regexp.new(string.chars.map { |c| t.encoded(c, **options) }.join) end
|
62
|
+
def from_nil(**options) qualified(with_lookahead(default(**options), **options), **options) end
|
63
|
+
def default(**options) "[^/\\?#]" end
|
64
|
+
end
|
65
|
+
|
66
|
+
# @!visibility private
|
67
|
+
class Splat < Capture
|
68
|
+
register :splat, :named_splat
|
69
|
+
# splats are always non-greedy
|
70
|
+
# @!visibility private
|
71
|
+
def pattern(**options)
|
72
|
+
".*?"
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
# @!visibility private
|
77
|
+
class Variable < Capture
|
78
|
+
register :variable
|
79
|
+
|
80
|
+
# @!visibility private
|
81
|
+
def translate(**options)
|
82
|
+
return super(**options) if explode or not options[:parametric]
|
83
|
+
parametric super(parametric: false, **options)
|
84
|
+
end
|
85
|
+
|
86
|
+
# @!visibility private
|
87
|
+
def pattern(parametric: false, separator: nil, **options)
|
88
|
+
register_param(parametric: parametric, separator: separator, **options)
|
89
|
+
pattern = super(**options)
|
90
|
+
pattern = parametric(pattern) if parametric
|
91
|
+
pattern = "#{pattern}(?:#{Regexp.escape(separator)}#{pattern})*" if explode and separator
|
92
|
+
pattern
|
93
|
+
end
|
94
|
+
|
95
|
+
# @!visibility private
|
96
|
+
def parametric(string)
|
97
|
+
"#{Regexp.escape(name)}(?:=#{string})?"
|
98
|
+
end
|
99
|
+
|
100
|
+
# @!visibility private
|
101
|
+
def qualified(string, **options)
|
102
|
+
prefix ? "#{string}{1,#{prefix}}" : super(string, **options)
|
103
|
+
end
|
104
|
+
|
105
|
+
# @!visibility private
|
106
|
+
def default(allow_reserved: false, **options)
|
107
|
+
allow_reserved ? '[\w\-\.~%\:/\?#\[\]@\!\$\&\'\(\)\*\+,;=]' : '[\w\-\.~%]'
|
108
|
+
end
|
109
|
+
|
110
|
+
# @!visibility private
|
111
|
+
def register_param(parametric: false, split_params: nil, separator: nil, **options)
|
112
|
+
return unless explode and split_params
|
113
|
+
split_params[name] = { separator: separator, parametric: parametric }
|
114
|
+
end
|
115
|
+
end
|
116
|
+
|
117
|
+
# @return [String] Regular expression for matching the given character in all representations
|
118
|
+
# @!visibility private
|
119
|
+
def encoded(char, uri_decode: true, space_matches_plus: true, **options)
|
120
|
+
return Regexp.escape(char) unless uri_decode
|
121
|
+
encoded = escape(char, escape: /./)
|
122
|
+
list = [escape(char), encoded.downcase, encoded.upcase].uniq.map { |c| Regexp.escape(c) }
|
123
|
+
list << encoded('+') if space_matches_plus and char == " "
|
124
|
+
"(?:%s)" % list.join("|")
|
125
|
+
end
|
126
|
+
|
127
|
+
# Compiles an AST to a regular expression.
|
128
|
+
# @param [Mustermann::AST::Node] ast the tree
|
129
|
+
# @return [Regexp] corresponding regular expression.
|
130
|
+
#
|
131
|
+
# @!visibility private
|
132
|
+
def self.compile(ast, **options)
|
133
|
+
new.compile(ast, **options)
|
134
|
+
end
|
135
|
+
|
136
|
+
# Compiles an AST to a regular expression.
|
137
|
+
# @param [Mustermann::AST::Node] ast the tree
|
138
|
+
# @return [Regexp] corresponding regular expression.
|
139
|
+
#
|
140
|
+
# @!visibility private
|
141
|
+
def compile(ast, except: nil, **options)
|
142
|
+
except &&= "(?!#{translate(except, no_captures: true, **options)}\\Z)"
|
143
|
+
expression = "\\A#{except}#{translate(ast, **options)}\\Z"
|
144
|
+
Regexp.new(expression)
|
145
|
+
end
|
146
|
+
end
|
147
|
+
|
148
|
+
private_constant :Compiler
|
149
|
+
end
|
150
|
+
end
|