mustermann 0.0.1 → 0.1.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 +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
@@ -0,0 +1,28 @@
|
|
1
|
+
module Support
|
2
|
+
module Pattern
|
3
|
+
def pattern(pattern, options = nil, &block)
|
4
|
+
description = "pattern %p" % pattern
|
5
|
+
|
6
|
+
if options
|
7
|
+
description << " with options %p" % [options]
|
8
|
+
instance = described_class.new(pattern, options)
|
9
|
+
else
|
10
|
+
instance = described_class.new(pattern)
|
11
|
+
end
|
12
|
+
|
13
|
+
context description do
|
14
|
+
subject(:pattern) { instance }
|
15
|
+
its(:to_s) { should be == pattern }
|
16
|
+
its(:inspect) { should be == "#<#{described_class}:#{pattern.inspect}>" }
|
17
|
+
its(:names) { should be_an(Array) }
|
18
|
+
|
19
|
+
example 'string should be immune to external change' do
|
20
|
+
subject.to_s.replace "NOT THE PATTERN"
|
21
|
+
subject.to_s.should be == pattern
|
22
|
+
end
|
23
|
+
|
24
|
+
instance_eval(&block)
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
metadata
CHANGED
@@ -1,36 +1,32 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: mustermann
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0
|
5
|
-
prerelease:
|
4
|
+
version: 0.1.0
|
6
5
|
platform: ruby
|
7
6
|
authors:
|
8
7
|
- Konstantin Haase
|
9
8
|
autorequire:
|
10
9
|
bindir: bin
|
11
10
|
cert_chain: []
|
12
|
-
date: 2013-
|
11
|
+
date: 2013-05-12 00:00:00.000000000 Z
|
13
12
|
dependencies:
|
14
13
|
- !ruby/object:Gem::Dependency
|
15
14
|
name: rspec
|
16
15
|
requirement: !ruby/object:Gem::Requirement
|
17
|
-
none: false
|
18
16
|
requirements:
|
19
|
-
- -
|
17
|
+
- - '>='
|
20
18
|
- !ruby/object:Gem::Version
|
21
19
|
version: '0'
|
22
20
|
type: :development
|
23
21
|
prerelease: false
|
24
22
|
version_requirements: !ruby/object:Gem::Requirement
|
25
|
-
none: false
|
26
23
|
requirements:
|
27
|
-
- -
|
24
|
+
- - '>='
|
28
25
|
- !ruby/object:Gem::Version
|
29
26
|
version: '0'
|
30
27
|
- !ruby/object:Gem::Dependency
|
31
28
|
name: sinatra
|
32
29
|
requirement: !ruby/object:Gem::Requirement
|
33
|
-
none: false
|
34
30
|
requirements:
|
35
31
|
- - ~>
|
36
32
|
- !ruby/object:Gem::Version
|
@@ -38,7 +34,6 @@ dependencies:
|
|
38
34
|
type: :development
|
39
35
|
prerelease: false
|
40
36
|
version_requirements: !ruby/object:Gem::Requirement
|
41
|
-
none: false
|
42
37
|
requirements:
|
43
38
|
- - ~>
|
44
39
|
- !ruby/object:Gem::Version
|
@@ -46,97 +41,85 @@ dependencies:
|
|
46
41
|
- !ruby/object:Gem::Dependency
|
47
42
|
name: rack-test
|
48
43
|
requirement: !ruby/object:Gem::Requirement
|
49
|
-
none: false
|
50
44
|
requirements:
|
51
|
-
- -
|
45
|
+
- - '>='
|
52
46
|
- !ruby/object:Gem::Version
|
53
47
|
version: '0'
|
54
48
|
type: :development
|
55
49
|
prerelease: false
|
56
50
|
version_requirements: !ruby/object:Gem::Requirement
|
57
|
-
none: false
|
58
51
|
requirements:
|
59
|
-
- -
|
52
|
+
- - '>='
|
60
53
|
- !ruby/object:Gem::Version
|
61
54
|
version: '0'
|
62
55
|
- !ruby/object:Gem::Dependency
|
63
56
|
name: rake
|
64
57
|
requirement: !ruby/object:Gem::Requirement
|
65
|
-
none: false
|
66
58
|
requirements:
|
67
|
-
- -
|
59
|
+
- - '>='
|
68
60
|
- !ruby/object:Gem::Version
|
69
61
|
version: '0'
|
70
62
|
type: :development
|
71
63
|
prerelease: false
|
72
64
|
version_requirements: !ruby/object:Gem::Requirement
|
73
|
-
none: false
|
74
65
|
requirements:
|
75
|
-
- -
|
66
|
+
- - '>='
|
76
67
|
- !ruby/object:Gem::Version
|
77
68
|
version: '0'
|
78
69
|
- !ruby/object:Gem::Dependency
|
79
70
|
name: yard
|
80
71
|
requirement: !ruby/object:Gem::Requirement
|
81
|
-
none: false
|
82
72
|
requirements:
|
83
|
-
- -
|
73
|
+
- - '>='
|
84
74
|
- !ruby/object:Gem::Version
|
85
75
|
version: '0'
|
86
76
|
type: :development
|
87
77
|
prerelease: false
|
88
78
|
version_requirements: !ruby/object:Gem::Requirement
|
89
|
-
none: false
|
90
79
|
requirements:
|
91
|
-
- -
|
80
|
+
- - '>='
|
92
81
|
- !ruby/object:Gem::Version
|
93
82
|
version: '0'
|
94
83
|
- !ruby/object:Gem::Dependency
|
95
84
|
name: redcarpet
|
96
85
|
requirement: !ruby/object:Gem::Requirement
|
97
|
-
none: false
|
98
86
|
requirements:
|
99
|
-
- -
|
87
|
+
- - '>='
|
100
88
|
- !ruby/object:Gem::Version
|
101
89
|
version: '0'
|
102
90
|
type: :development
|
103
91
|
prerelease: false
|
104
92
|
version_requirements: !ruby/object:Gem::Requirement
|
105
|
-
none: false
|
106
93
|
requirements:
|
107
|
-
- -
|
94
|
+
- - '>='
|
108
95
|
- !ruby/object:Gem::Version
|
109
96
|
version: '0'
|
110
97
|
- !ruby/object:Gem::Dependency
|
111
98
|
name: simplecov
|
112
99
|
requirement: !ruby/object:Gem::Requirement
|
113
|
-
none: false
|
114
100
|
requirements:
|
115
|
-
- -
|
101
|
+
- - '>='
|
116
102
|
- !ruby/object:Gem::Version
|
117
103
|
version: '0'
|
118
104
|
type: :development
|
119
105
|
prerelease: false
|
120
106
|
version_requirements: !ruby/object:Gem::Requirement
|
121
|
-
none: false
|
122
107
|
requirements:
|
123
|
-
- -
|
108
|
+
- - '>='
|
124
109
|
- !ruby/object:Gem::Version
|
125
110
|
version: '0'
|
126
111
|
- !ruby/object:Gem::Dependency
|
127
112
|
name: coveralls
|
128
113
|
requirement: !ruby/object:Gem::Requirement
|
129
|
-
none: false
|
130
114
|
requirements:
|
131
|
-
- -
|
115
|
+
- - '>='
|
132
116
|
- !ruby/object:Gem::Version
|
133
117
|
version: '0'
|
134
118
|
type: :development
|
135
119
|
prerelease: false
|
136
120
|
version_requirements: !ruby/object:Gem::Requirement
|
137
|
-
none: false
|
138
121
|
requirements:
|
139
|
-
- -
|
122
|
+
- - '>='
|
140
123
|
- !ruby/object:Gem::Version
|
141
124
|
version: '0'
|
142
125
|
description: library implementing patterns that behave like regular expressions
|
@@ -145,11 +128,12 @@ executables: []
|
|
145
128
|
extensions: []
|
146
129
|
extra_rdoc_files:
|
147
130
|
- README.md
|
131
|
+
- internals.md
|
148
132
|
files:
|
149
133
|
- .gitignore
|
150
134
|
- .rspec
|
151
|
-
- .test_queue_stats
|
152
135
|
- .travis.yml
|
136
|
+
- .yardopts
|
153
137
|
- Gemfile
|
154
138
|
- LICENSE
|
155
139
|
- README.md
|
@@ -157,8 +141,17 @@ files:
|
|
157
141
|
- bench/capturing.rb
|
158
142
|
- bench/simple_vs_sinatra.rb
|
159
143
|
- bench/template_vs_addressable.rb
|
144
|
+
- internals.md
|
160
145
|
- lib/mustermann.rb
|
161
|
-
- lib/mustermann/ast.rb
|
146
|
+
- lib/mustermann/ast/compiler.rb
|
147
|
+
- lib/mustermann/ast/expander.rb
|
148
|
+
- lib/mustermann/ast/node.rb
|
149
|
+
- lib/mustermann/ast/parser.rb
|
150
|
+
- lib/mustermann/ast/pattern.rb
|
151
|
+
- lib/mustermann/ast/transformer.rb
|
152
|
+
- lib/mustermann/ast/translator.rb
|
153
|
+
- lib/mustermann/ast/tree_renderer.rb
|
154
|
+
- lib/mustermann/ast/validation.rb
|
162
155
|
- lib/mustermann/error.rb
|
163
156
|
- lib/mustermann/extension.rb
|
164
157
|
- lib/mustermann/identity.rb
|
@@ -172,7 +165,6 @@ files:
|
|
172
165
|
- lib/mustermann/template.rb
|
173
166
|
- lib/mustermann/version.rb
|
174
167
|
- mustermann.gemspec
|
175
|
-
- spec/ast_spec.rb
|
176
168
|
- spec/extension_spec.rb
|
177
169
|
- spec/identity_spec.rb
|
178
170
|
- spec/mustermann_spec.rb
|
@@ -184,34 +176,37 @@ files:
|
|
184
176
|
- spec/simple_spec.rb
|
185
177
|
- spec/sinatra_spec.rb
|
186
178
|
- spec/support.rb
|
179
|
+
- spec/support/coverage.rb
|
180
|
+
- spec/support/env.rb
|
181
|
+
- spec/support/expand_matcher.rb
|
182
|
+
- spec/support/match_matcher.rb
|
183
|
+
- spec/support/pattern.rb
|
187
184
|
- spec/template_spec.rb
|
188
185
|
homepage: https://github.com/rkh/mustermann
|
189
186
|
licenses:
|
190
187
|
- MIT
|
188
|
+
metadata: {}
|
191
189
|
post_install_message:
|
192
190
|
rdoc_options: []
|
193
191
|
require_paths:
|
194
192
|
- lib
|
195
193
|
required_ruby_version: !ruby/object:Gem::Requirement
|
196
|
-
none: false
|
197
194
|
requirements:
|
198
|
-
- -
|
195
|
+
- - '>='
|
199
196
|
- !ruby/object:Gem::Version
|
200
197
|
version: 2.0.0
|
201
198
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
202
|
-
none: false
|
203
199
|
requirements:
|
204
|
-
- -
|
200
|
+
- - '>='
|
205
201
|
- !ruby/object:Gem::Version
|
206
202
|
version: '0'
|
207
203
|
requirements: []
|
208
204
|
rubyforge_project:
|
209
|
-
rubygems_version:
|
205
|
+
rubygems_version: 2.0.0
|
210
206
|
signing_key:
|
211
|
-
specification_version:
|
207
|
+
specification_version: 4
|
212
208
|
summary: use patterns like regular expressions
|
213
209
|
test_files:
|
214
|
-
- spec/ast_spec.rb
|
215
210
|
- spec/extension_spec.rb
|
216
211
|
- spec/identity_spec.rb
|
217
212
|
- spec/mustermann_spec.rb
|
@@ -223,5 +218,10 @@ test_files:
|
|
223
218
|
- spec/simple_spec.rb
|
224
219
|
- spec/sinatra_spec.rb
|
225
220
|
- spec/support.rb
|
221
|
+
- spec/support/coverage.rb
|
222
|
+
- spec/support/env.rb
|
223
|
+
- spec/support/expand_matcher.rb
|
224
|
+
- spec/support/match_matcher.rb
|
225
|
+
- spec/support/pattern.rb
|
226
226
|
- spec/template_spec.rb
|
227
227
|
has_rdoc:
|
data/.test_queue_stats
DELETED
Binary file
|
data/lib/mustermann/ast.rb
DELETED
@@ -1,403 +0,0 @@
|
|
1
|
-
require 'mustermann/regexp_based'
|
2
|
-
require 'strscan'
|
3
|
-
|
4
|
-
module Mustermann
|
5
|
-
# Superclass for pattern styles that parse an AST from the string pattern.
|
6
|
-
# @abstract
|
7
|
-
class AST < RegexpBased
|
8
|
-
supported_options :capture, :except, :greedy, :space_matches_plus
|
9
|
-
|
10
|
-
# @!visibility private
|
11
|
-
class Node
|
12
|
-
# @!visibility private
|
13
|
-
attr_accessor :payload
|
14
|
-
|
15
|
-
# Helper for creating a new instance and calling #parse on it.
|
16
|
-
# @return [Node]
|
17
|
-
# @!visibility private
|
18
|
-
def self.parse(element = new, &block)
|
19
|
-
element.tap { |n| n.parse(&block) }
|
20
|
-
end
|
21
|
-
|
22
|
-
# @!visibility private
|
23
|
-
def initialize(payload = nil, **options)
|
24
|
-
options.each { |key, value| public_send("#{key}=", value) }
|
25
|
-
self.payload = payload
|
26
|
-
end
|
27
|
-
|
28
|
-
# Double dispatch helper for reading from the buffer into the payload.
|
29
|
-
# @!visibility private
|
30
|
-
def parse
|
31
|
-
self.payload ||= []
|
32
|
-
while element = yield
|
33
|
-
payload << element
|
34
|
-
end
|
35
|
-
end
|
36
|
-
|
37
|
-
# @return [String] regular expression corresponding to node
|
38
|
-
# @!visibility private
|
39
|
-
def compile(options)
|
40
|
-
Array(payload).map { |e| e.compile(options) }.join
|
41
|
-
end
|
42
|
-
|
43
|
-
# @return [Node] This node after tree transformation. Might be self.
|
44
|
-
# @!visibility private
|
45
|
-
def transform
|
46
|
-
self.payload = payload.transform if payload.respond_to? :transform
|
47
|
-
return self unless Array === payload
|
48
|
-
|
49
|
-
new_payload = []
|
50
|
-
with_lookahead = []
|
51
|
-
|
52
|
-
payload.each do |element|
|
53
|
-
element = element.transform
|
54
|
-
if with_lookahead.empty?
|
55
|
-
list = element.expect_lookahead? ? with_lookahead : new_payload
|
56
|
-
list << element
|
57
|
-
elsif element.lookahead?
|
58
|
-
with_lookahead << element
|
59
|
-
else
|
60
|
-
with_lookahead = [WithLookAhead.new(with_lookahead, false)] if element.separator? and with_lookahead.size > 1
|
61
|
-
new_payload.concat(with_lookahead)
|
62
|
-
new_payload << element
|
63
|
-
with_lookahead.clear
|
64
|
-
end
|
65
|
-
end
|
66
|
-
|
67
|
-
with_lookahead = [WithLookAhead.new(with_lookahead, true)] if with_lookahead.size > 1
|
68
|
-
new_payload.concat(with_lookahead)
|
69
|
-
@payload = new_payload
|
70
|
-
self
|
71
|
-
end
|
72
|
-
|
73
|
-
# @!visibility private
|
74
|
-
def separator?
|
75
|
-
false
|
76
|
-
end
|
77
|
-
|
78
|
-
# @!visibility private
|
79
|
-
def lookahead?(in_lookahead = false)
|
80
|
-
false
|
81
|
-
end
|
82
|
-
|
83
|
-
# @!visibility private
|
84
|
-
def expect_lookahead?
|
85
|
-
false
|
86
|
-
end
|
87
|
-
|
88
|
-
# @return [String] Regular expression for matching the given character in all representations
|
89
|
-
# @!visibility private
|
90
|
-
def encoded(char, uri_decode, space_matches_plus)
|
91
|
-
return Regexp.escape(char) unless uri_decode
|
92
|
-
uri_parser = URI::Parser.new
|
93
|
-
encoded = uri_parser.escape(char, /./)
|
94
|
-
list = [uri_parser.escape(char), encoded.downcase, encoded.upcase].uniq.map { |c| Regexp.escape(c) }
|
95
|
-
list << encoded('+', uri_decode, space_matches_plus) if space_matches_plus and char == " "
|
96
|
-
"(?:%s)" % list.join("|")
|
97
|
-
end
|
98
|
-
|
99
|
-
# @return [Array<String>]
|
100
|
-
# @!visibility private
|
101
|
-
def capture_names
|
102
|
-
return payload.capture_names if payload.respond_to? :capture_names
|
103
|
-
return [] unless payload.respond_to? :map
|
104
|
-
payload.map { |e| e.capture_names if e.respond_to? :capture_names }
|
105
|
-
end
|
106
|
-
end
|
107
|
-
|
108
|
-
# @!visibility private
|
109
|
-
class Char < Node
|
110
|
-
# @!visibility private
|
111
|
-
def compile(uri_decode: true, space_matches_plus: true, **options)
|
112
|
-
encoded(payload, uri_decode, space_matches_plus)
|
113
|
-
end
|
114
|
-
|
115
|
-
# @!visibility private
|
116
|
-
def lookahead?(in_lookahead = false)
|
117
|
-
in_lookahead
|
118
|
-
end
|
119
|
-
|
120
|
-
# @return [String] regexp to be used in lookahead for semi-greedy capturing
|
121
|
-
# @!visibility private
|
122
|
-
def lookahead(ahead, options)
|
123
|
-
ahead + compile(options)
|
124
|
-
end
|
125
|
-
end
|
126
|
-
|
127
|
-
# @!visibility private
|
128
|
-
class Separator < Node
|
129
|
-
# @!visibility private
|
130
|
-
def compile(options)
|
131
|
-
Regexp.escape(payload)
|
132
|
-
end
|
133
|
-
|
134
|
-
# @!visibility private
|
135
|
-
def separator?
|
136
|
-
true
|
137
|
-
end
|
138
|
-
end
|
139
|
-
|
140
|
-
# @!visibility private
|
141
|
-
class Optional < Node
|
142
|
-
# @return [String] regexp to be used in lookahead for semi-greedy capturing
|
143
|
-
# @!visibility private
|
144
|
-
def lookahead(ahead, options)
|
145
|
-
payload.lookahead(ahead, options)
|
146
|
-
end
|
147
|
-
|
148
|
-
# @!visibility private
|
149
|
-
def compile(options)
|
150
|
-
"(?:%s)?" % payload.compile(options)
|
151
|
-
end
|
152
|
-
|
153
|
-
# @!visibility private
|
154
|
-
def lookahead?(in_lookahead = false)
|
155
|
-
payload.lookahead? true or payload.expect_lookahead?
|
156
|
-
end
|
157
|
-
end
|
158
|
-
|
159
|
-
# @!visibility private
|
160
|
-
class Group < Node
|
161
|
-
# @!visibility private
|
162
|
-
def initialize(payload = nil, **options)
|
163
|
-
super(Array(payload), **options)
|
164
|
-
end
|
165
|
-
|
166
|
-
# @!visibility private
|
167
|
-
def lookahead?(in_lookahead = false)
|
168
|
-
return false unless payload[0..-2].all? { |e| e.lookahead? in_lookahead }
|
169
|
-
payload.last.expect_lookahead? or payload.last.lookahead? in_lookahead
|
170
|
-
end
|
171
|
-
|
172
|
-
# @!visibility private
|
173
|
-
def transform
|
174
|
-
payload.size == 1 ? payload.first.transform : super
|
175
|
-
end
|
176
|
-
|
177
|
-
# @!visibility private
|
178
|
-
def parse
|
179
|
-
super
|
180
|
-
rescue UnexpectedClosingGroup
|
181
|
-
end
|
182
|
-
|
183
|
-
# @return [String] regexp to be used in lookahead for semi-greedy capturing
|
184
|
-
# @!visibility private
|
185
|
-
def lookahead(ahead, options)
|
186
|
-
payload.inject(ahead) { |a,e| e.lookahead(a, options) }
|
187
|
-
end
|
188
|
-
end
|
189
|
-
|
190
|
-
# @!visibility private
|
191
|
-
class Capture < Node
|
192
|
-
# @!visibility private
|
193
|
-
def expect_lookahead?
|
194
|
-
true
|
195
|
-
end
|
196
|
-
|
197
|
-
# @!visibility private
|
198
|
-
def parse
|
199
|
-
self.payload ||= ""
|
200
|
-
super
|
201
|
-
end
|
202
|
-
|
203
|
-
# @!visibility private
|
204
|
-
def capture_names
|
205
|
-
[name]
|
206
|
-
end
|
207
|
-
|
208
|
-
# @!visibility private
|
209
|
-
def name
|
210
|
-
raise CompileError, "capture name can't be empty" if payload.nil? or payload.empty?
|
211
|
-
raise CompileError, "capture name must start with underscore or lower case letter" unless payload =~ /^[a-z_]/
|
212
|
-
raise CompileError, "capture name can't be #{payload}" if payload == "splat" or payload == "captures"
|
213
|
-
payload
|
214
|
-
end
|
215
|
-
|
216
|
-
# @return [String] regexp without the named capture
|
217
|
-
# @!visibility private
|
218
|
-
def pattern(capture: nil, **options)
|
219
|
-
case capture
|
220
|
-
when Symbol then from_symbol(capture, **options)
|
221
|
-
when Array then from_array(capture, **options)
|
222
|
-
when Hash then from_hash(capture, **options)
|
223
|
-
when String then from_string(capture, **options)
|
224
|
-
when nil then from_nil(**options)
|
225
|
-
else capture
|
226
|
-
end
|
227
|
-
end
|
228
|
-
|
229
|
-
# @return [String] regexp to be used in lookahead for semi-greedy capturing
|
230
|
-
# @!visibility private
|
231
|
-
def lookahead(ahead, options)
|
232
|
-
ahead + pattern(lookahead: ahead, greedy: false, **options).to_s
|
233
|
-
end
|
234
|
-
|
235
|
-
# @!visibility private
|
236
|
-
def compile(options)
|
237
|
-
return pattern(options) if options[:no_captures]
|
238
|
-
"(?<#{name}>#{compile(no_captures: true, **options)})"
|
239
|
-
end
|
240
|
-
|
241
|
-
private
|
242
|
-
|
243
|
-
def qualified(string, greedy: true, **options)
|
244
|
-
"#{string}+#{?? unless greedy}"
|
245
|
-
end
|
246
|
-
|
247
|
-
def default(**options)
|
248
|
-
"[^/\\?#]"
|
249
|
-
end
|
250
|
-
|
251
|
-
def from_nil(**options)
|
252
|
-
qualified(with_lookahead(default(**options), **options), **options)
|
253
|
-
end
|
254
|
-
|
255
|
-
def from_hash(hash, **options)
|
256
|
-
entry = hash[name.to_sym]
|
257
|
-
pattern(capture: entry, **options)
|
258
|
-
end
|
259
|
-
|
260
|
-
def from_array(array, **options)
|
261
|
-
array = array.map { |e| pattern(capture: e, **options) }
|
262
|
-
Regexp.union(*array)
|
263
|
-
end
|
264
|
-
|
265
|
-
def from_symbol(symbol, **options)
|
266
|
-
qualified(with_lookahead("[[:#{symbol}:]]", **options), **options)
|
267
|
-
end
|
268
|
-
|
269
|
-
def from_string(string, uri_decode: true, space_matches_plus: true, **options)
|
270
|
-
Regexp.new(string.chars.map { |c| encoded(c, uri_decode, space_matches_plus) }.join)
|
271
|
-
end
|
272
|
-
|
273
|
-
def with_lookahead(string, lookahead: nil, **options)
|
274
|
-
return string unless lookahead
|
275
|
-
"(?:(?!#{lookahead})#{string})"
|
276
|
-
end
|
277
|
-
end
|
278
|
-
|
279
|
-
# @!visibility private
|
280
|
-
class Splat < Capture
|
281
|
-
# @!visibility private
|
282
|
-
def expect_lookahead?
|
283
|
-
false
|
284
|
-
end
|
285
|
-
|
286
|
-
# @!visibility private
|
287
|
-
def name
|
288
|
-
"splat"
|
289
|
-
end
|
290
|
-
|
291
|
-
# @!visibility private
|
292
|
-
def pattern(options)
|
293
|
-
".*?"
|
294
|
-
end
|
295
|
-
end
|
296
|
-
|
297
|
-
# @!visibility private
|
298
|
-
class NamedSplat < Splat
|
299
|
-
alias_method :name, :payload
|
300
|
-
end
|
301
|
-
|
302
|
-
# @!visibility private
|
303
|
-
class WithLookAhead < Node
|
304
|
-
# @!visibility private
|
305
|
-
attr_accessor :head, :at_end
|
306
|
-
|
307
|
-
# @!visibility private
|
308
|
-
def initialize(payload, at_end)
|
309
|
-
self.head, *self.payload = payload
|
310
|
-
self.at_end = at_end
|
311
|
-
end
|
312
|
-
|
313
|
-
# @!visibility private
|
314
|
-
def compile(options)
|
315
|
-
lookahead = payload.inject('') { |l,e| e.lookahead(l, options) }
|
316
|
-
lookahead << (at_end ? '$' : '/')
|
317
|
-
head.compile(lookahead: lookahead, **options) + super
|
318
|
-
end
|
319
|
-
end
|
320
|
-
|
321
|
-
# @!visibility private
|
322
|
-
class Root < Node
|
323
|
-
# @!visibility private
|
324
|
-
attr_accessor :pattern
|
325
|
-
|
326
|
-
# @!visibility private
|
327
|
-
def self.parse(string, &block)
|
328
|
-
root = new
|
329
|
-
root.pattern = string
|
330
|
-
super(root, &block).transform
|
331
|
-
end
|
332
|
-
|
333
|
-
# @!visibility private
|
334
|
-
def capture_names
|
335
|
-
super.flatten
|
336
|
-
end
|
337
|
-
|
338
|
-
# @!visibility private
|
339
|
-
def check_captures
|
340
|
-
names = capture_names
|
341
|
-
names.delete("splat")
|
342
|
-
raise CompileError, "can't use the same capture name twice" if names.uniq != names
|
343
|
-
end
|
344
|
-
|
345
|
-
# @!visibility private
|
346
|
-
def compile(except: nil, **options)
|
347
|
-
check_captures
|
348
|
-
except &&= "(?!#{except}\\Z)"
|
349
|
-
Regexp.new("\\A#{except}#{super(options)}\\Z")
|
350
|
-
rescue CompileError => e
|
351
|
-
e.message << ": #{pattern.inspect}"
|
352
|
-
raise e
|
353
|
-
end
|
354
|
-
end
|
355
|
-
|
356
|
-
# @!visibility private
|
357
|
-
def parse(string)
|
358
|
-
buffer = StringScanner.new(string)
|
359
|
-
Root.parse(string) { parse_buffer(buffer) unless buffer.eos? }
|
360
|
-
rescue ParseError => e
|
361
|
-
e.message << " while parsing #{string.inspect}"
|
362
|
-
raise e
|
363
|
-
end
|
364
|
-
|
365
|
-
# @!visibility private
|
366
|
-
def compile(string, except: nil, **options)
|
367
|
-
options[:except] = compile(except, no_captures: true, **options) if except
|
368
|
-
parse(string).compile(options)
|
369
|
-
end
|
370
|
-
|
371
|
-
# @!visibility private
|
372
|
-
def parse_buffer(buffer)
|
373
|
-
parse_suffix(parse_element(buffer), buffer)
|
374
|
-
end
|
375
|
-
|
376
|
-
# @!visibility private
|
377
|
-
def parse_element(buffer)
|
378
|
-
raise NotImplementedError, 'subclass responsibility'
|
379
|
-
end
|
380
|
-
|
381
|
-
# @!visibility private
|
382
|
-
def parse_suffix(element, buffer)
|
383
|
-
element
|
384
|
-
end
|
385
|
-
|
386
|
-
# @!visibility private
|
387
|
-
def unexpected(char, exception: ParseError)
|
388
|
-
char = char.getch if char.respond_to? :getch
|
389
|
-
char = "space" if char == " "
|
390
|
-
raise exception, "unexpected #{char || "end of string"}"
|
391
|
-
end
|
392
|
-
|
393
|
-
# @!visibility private
|
394
|
-
def expect(buffer, regexp, **options)
|
395
|
-
regexp = Regexp.new Regexp.escape(regexp.to_str) unless regexp.is_a? Regexp
|
396
|
-
string = buffer.scan(regexp) || unexpected(buffer, **options)
|
397
|
-
regexp.names.any? ? regexp.match(string) : string
|
398
|
-
end
|
399
|
-
|
400
|
-
private :parse, :compile, :parse_buffer, :parse_element, :parse_suffix, :unexpected, :expect
|
401
|
-
private_constant :Node, :Char, :Separator, :Optional, :Group, :Capture, :Splat, :NamedSplat, :WithLookAhead, :Root
|
402
|
-
end
|
403
|
-
end
|