mustermann-simple 0.4.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 1ea32ffc659a85d4e2f1f0670e2f187b119f0ec0
4
+ data.tar.gz: c3079a0b6784c514512e7a6f9d71bd8c809ceb6e
5
+ SHA512:
6
+ metadata.gz: cefc68dec41e61edaf025029c447a192f44d01d95abe405505dc8554a94823d95e722a39d54149b8c90978a21a83676afc37b6cdb35021e5821bf8e099eb613b
7
+ data.tar.gz: 12a7a8d205961cd6a41e74f0b5acf183cda02b0f59dd476d224c156e6e564d56eb5c4214ff9d9030526ca4f02c183cbe69c853a44bab63cce10343ff58483935
@@ -0,0 +1,76 @@
1
+ # Simple Syntax for Mustermann
2
+
3
+ This gem implements the `simple` pattern type for Mustermann. It is compatible with [Sinatra](http://www.sinatrarb.com/) (1.x), [Scalatra](http://www.scalatra.org/) and [Dancer](http://perldancer.org/).
4
+
5
+ ## Overview
6
+
7
+ **Supported options:**
8
+ `greedy`, `space_matches_plus`, `uri_decode` and `ignore_unknown_options`.
9
+
10
+ This is useful for porting an application that relies on this behavior to a later Sinatra version and to make sure Sinatra 2.0 patterns do not decrease performance. Simple patterns internally use the same code older Sinatra versions used for compiling the pattern. Error messages for broken patterns will therefore not be as informative as for other pattern implementations.
11
+
12
+ ``` ruby
13
+ require 'mustermann'
14
+
15
+ pattern = Mustermann.new('/:example', type: :simple)
16
+ pattern === "/foo.bar" # => true
17
+ pattern === "/foo/bar" # => false
18
+ pattern.params("/foo.bar") # => { "example" => "foo.bar" }
19
+ pattern.params("/foo/bar") # => nil
20
+
21
+ pattern = Mustermann.new('/:example/?:optional?', type: :simple)
22
+ pattern === "/foo.bar" # => true
23
+ pattern === "/foo/bar" # => true
24
+ pattern.params("/foo.bar") # => { "example" => "foo.bar", "optional" => nil }
25
+ pattern.params("/foo/bar") # => { "example" => "foo", "optional" => "bar" }
26
+
27
+ pattern = Mustermann.new('/*', type: :simple)
28
+ pattern === "/foo.bar" # => true
29
+ pattern === "/foo/bar" # => true
30
+ pattern.params("/foo.bar") # => { "splat" => ["foo.bar"] }
31
+ pattern.params("/foo/bar") # => { "splat" => ["foo/bar"] }
32
+ ```
33
+
34
+ ## Syntax
35
+
36
+ <table>
37
+ <thead>
38
+ <tr>
39
+ <th>Syntax Element</th>
40
+ <th>Description</th>
41
+ </tr>
42
+ </thead>
43
+ <tbody>
44
+ <tr>
45
+ <td><b>:</b><i>name</i></td>
46
+ <td>
47
+ Captures anything but a forward slash in a greedy fashion. Capture is named <i>name</i>.
48
+ </td>
49
+ </tr>
50
+ <tr>
51
+ <td><b>*</b></td>
52
+ <td>
53
+ Captures anything in a non-greedy fashion. Capture is named splat.
54
+ It is always an array of captures, as you can use <tt>*</tt> more than once in a pattern.
55
+ </td>
56
+ </tr>
57
+ <tr>
58
+ <td><i>x</i><b>?</b></td>
59
+ <td>Makes <i>x</i> optional. For instance <tt>foo?</tt> matches <tt>foo</tt> or <tt>fo</tt>.</td>
60
+ </tr>
61
+ <tr>
62
+ <td><b>/</b></td>
63
+ <td>
64
+ Matches forward slash. Does not match URI encoded version of forward slash.
65
+ </td>
66
+ </tr>
67
+ <tr>
68
+ <td><i>any special character</i></td>
69
+ <td>Matches exactly that character or a URI encoded version of it.</td>
70
+ </tr>
71
+ <tr>
72
+ <td><i>any other character</i></td>
73
+ <td>Matches exactly that character.</td>
74
+ </tr>
75
+ </tbody>
76
+ </table>
@@ -0,0 +1,50 @@
1
+ require 'mustermann'
2
+ require 'mustermann/regexp_based'
3
+
4
+ module Mustermann
5
+ # Sinatra 1.3 style pattern implementation.
6
+ #
7
+ # @example
8
+ # Mustermann.new('/:foo', type: :simple) === '/bar' # => true
9
+ #
10
+ # @see Mustermann::Pattern
11
+ # @see file:README.md#simple Syntax description in the README
12
+ class Simple < RegexpBased
13
+ register :simple
14
+ supported_options :greedy, :space_matches_plus
15
+ instance_delegate highlighter: 'self.class'
16
+
17
+ # @!visibility private
18
+ # @return [#highlight, nil]
19
+ # highlighing logic for mustermann-visualizer,
20
+ # nil if mustermann-visualizer hasn't been loaded
21
+ def self.highlighter
22
+ return unless defined? Mustermann::Visualizer::Highlighter
23
+ @highlighter ||= Mustermann::Visualizer::Highlighter.create do
24
+ on(/:(\w+)/) { |matched| element(:capture, ':') { element(:name, matched[1..-1]) } }
25
+ on("*" => :splat, "?" => :optional)
26
+ end
27
+ end
28
+
29
+ def compile(greedy: true, uri_decode: true, space_matches_plus: true, **options)
30
+ pattern = @string.gsub(/[^\?\%\\\/\:\*\w]/) { |c| encoded(c, uri_decode, space_matches_plus) }
31
+ pattern.gsub!(/((:\w+)|\*)/) do |match|
32
+ match == "*" ? "(?<splat>.*?)" : "(?<#{$2[1..-1]}>[^/?#]+#{?? unless greedy})"
33
+ end
34
+ Regexp.new(pattern)
35
+ rescue SyntaxError, RegexpError => error
36
+ type = error.message["invalid group name"] ? CompileError : ParseError
37
+ raise type, error.message, error.backtrace
38
+ end
39
+
40
+ def encoded(char, uri_decode, space_matches_plus)
41
+ return Regexp.escape(char) unless uri_decode
42
+ parser = URI::Parser.new
43
+ encoded = Regexp.union(parser.escape(char), parser.escape(char, /./).downcase, parser.escape(char, /./).upcase)
44
+ encoded = Regexp.union(encoded, encoded('+', true, true)) if space_matches_plus and char == " "
45
+ encoded
46
+ end
47
+
48
+ private :compile, :encoded
49
+ end
50
+ end
@@ -0,0 +1,18 @@
1
+ $:.unshift File.expand_path("../../mustermann/lib", __FILE__)
2
+ require "mustermann/version"
3
+
4
+ Gem::Specification.new do |s|
5
+ s.name = "mustermann-simple"
6
+ s.version = Mustermann::VERSION
7
+ s.author = "Konstantin Haase"
8
+ s.email = "konstantin.mailinglists@googlemail.com"
9
+ s.homepage = "https://github.com/rkh/mustermann"
10
+ s.summary = %q{Simple syntax for Mustermann}
11
+ s.description = %q{Adds Sinatra 1.x style patterns to Mustermman}
12
+ s.license = 'MIT'
13
+ s.required_ruby_version = '>= 2.1.0'
14
+ s.files = `git ls-files`.split("\n")
15
+ s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
16
+ s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
17
+ s.add_dependency 'mustermann', Mustermann::VERSION
18
+ end
@@ -0,0 +1,268 @@
1
+ require 'support'
2
+ require 'mustermann/simple'
3
+ require 'mustermann/visualizer'
4
+
5
+ describe Mustermann::Simple do
6
+ extend Support::Pattern
7
+
8
+ pattern '' do
9
+ it { should match('') }
10
+ it { should_not match('/') }
11
+
12
+ it { should_not respond_to(:expand) }
13
+ it { should_not respond_to(:to_templates) }
14
+ end
15
+
16
+ pattern '/' do
17
+ it { should match('/') }
18
+ it { should_not match('/foo') }
19
+ end
20
+
21
+ pattern '/foo' do
22
+ it { should match('/foo') }
23
+ it { should_not match('/bar') }
24
+ it { should_not match('/foo.bar') }
25
+ end
26
+
27
+ pattern '/foo/bar' do
28
+ it { should match('/foo/bar') }
29
+ it { should_not match('/foo%2Fbar') }
30
+ it { should_not match('/foo%2fbar') }
31
+ end
32
+
33
+ pattern '/:foo' do
34
+ it { should match('/foo') .capturing foo: 'foo' }
35
+ it { should match('/bar') .capturing foo: 'bar' }
36
+ it { should match('/foo.bar') .capturing foo: 'foo.bar' }
37
+ it { should match('/%0Afoo') .capturing foo: '%0Afoo' }
38
+ it { should match('/foo%2Fbar') .capturing foo: 'foo%2Fbar' }
39
+
40
+ it { should_not match('/foo?') }
41
+ it { should_not match('/foo/bar') }
42
+ it { should_not match('/') }
43
+ it { should_not match('/foo/') }
44
+ end
45
+
46
+ pattern '/föö' do
47
+ it { should match("/f%C3%B6%C3%B6") }
48
+ end
49
+
50
+ pattern "/:foo/:bar" do
51
+ it { should match('/foo/bar') .capturing foo: 'foo', bar: 'bar' }
52
+ it { should match('/foo.bar/bar.foo') .capturing foo: 'foo.bar', bar: 'bar.foo' }
53
+ it { should match('/user@example.com/name') .capturing foo: 'user@example.com', bar: 'name' }
54
+ it { should match('/10.1/te.st') .capturing foo: '10.1', bar: 'te.st' }
55
+ it { should match('/10.1.2/te.st') .capturing foo: '10.1.2', bar: 'te.st' }
56
+
57
+ it { should_not match('/foo%2Fbar') }
58
+ it { should_not match('/foo%2fbar') }
59
+
60
+ example { pattern.params('/bar/foo').should be == {"foo" => "bar", "bar" => "foo"} }
61
+ example { pattern.params('').should be_nil }
62
+ end
63
+
64
+ pattern '/hello/:person' do
65
+ it { should match('/hello/Frank').capturing person: 'Frank' }
66
+ end
67
+
68
+ pattern '/?:foo?/?:bar?' do
69
+ it { should match('/hello/world') .capturing foo: 'hello', bar: 'world' }
70
+ it { should match('/hello') .capturing foo: 'hello', bar: nil }
71
+ it { should match('/') .capturing foo: nil, bar: nil }
72
+ it { should match('') .capturing foo: nil, bar: nil }
73
+
74
+ it { should_not match('/hello/world/') }
75
+ end
76
+
77
+ pattern '/*' do
78
+ it { should match('/') .capturing splat: '' }
79
+ it { should match('/foo') .capturing splat: 'foo' }
80
+ it { should match('/foo/bar') .capturing splat: 'foo/bar' }
81
+
82
+ example { pattern.params('/foo').should be == {"splat" => ["foo"]} }
83
+ end
84
+
85
+ pattern '/:foo/*' do
86
+ it { should match("/foo/bar/baz") .capturing foo: 'foo', splat: 'bar/baz' }
87
+ it { should match("/foo/") .capturing foo: 'foo', splat: '' }
88
+ it { should match('/h%20w/h%20a%20y') .capturing foo: 'h%20w', splat: 'h%20a%20y' }
89
+ it { should_not match('/foo') }
90
+
91
+ example { pattern.params('/bar/foo').should be == {"splat" => ["foo"], "foo" => "bar"} }
92
+ example { pattern.params('/bar/foo/f%20o').should be == {"splat" => ["foo/f o"], "foo" => "bar"} }
93
+ end
94
+
95
+ pattern '/test$/' do
96
+ it { should match('/test$/') }
97
+ end
98
+
99
+ pattern '/te+st/' do
100
+ it { should match('/te+st/') }
101
+ it { should_not match('/test/') }
102
+ it { should_not match('/teest/') }
103
+ end
104
+
105
+ pattern "/path with spaces" do
106
+ it { should match('/path%20with%20spaces') }
107
+ it { should match('/path%2Bwith%2Bspaces') }
108
+ it { should match('/path+with+spaces') }
109
+ end
110
+
111
+ pattern '/foo&bar' do
112
+ it { should match('/foo&bar') }
113
+ end
114
+
115
+ pattern '/*/:foo/*/*' do
116
+ it { should match('/bar/foo/bling/baz/boom') }
117
+
118
+ it "should capture all splat parts" do
119
+ match = pattern.match('/bar/foo/bling/baz/boom')
120
+ match.captures.should be == ['bar', 'foo', 'bling', 'baz/boom']
121
+ match.names.should be == ['splat', 'foo']
122
+ end
123
+
124
+ it 'should map to proper params' do
125
+ pattern.params('/bar/foo/bling/baz/boom').should be == {
126
+ "foo" => "foo", "splat" => ['bar', 'bling', 'baz/boom']
127
+ }
128
+ end
129
+ end
130
+
131
+ pattern '/test.bar' do
132
+ it { should match('/test.bar') }
133
+ it { should_not match('/test0bar') }
134
+ end
135
+
136
+ pattern '/:file.:ext' do
137
+ it { should match('/pony.jpg') .capturing file: 'pony', ext: 'jpg' }
138
+ it { should match('/pony%2Ejpg') .capturing file: 'pony', ext: 'jpg' }
139
+ it { should match('/pony%2ejpg') .capturing file: 'pony', ext: 'jpg' }
140
+
141
+ it { should match('/pony%E6%AD%A3%2Ejpg') .capturing file: 'pony%E6%AD%A3', ext: 'jpg' }
142
+ it { should match('/pony%e6%ad%a3%2ejpg') .capturing file: 'pony%e6%ad%a3', ext: 'jpg' }
143
+ it { should match('/pony正%2Ejpg') .capturing file: 'pony正', ext: 'jpg' }
144
+ it { should match('/pony正%2ejpg') .capturing file: 'pony正', ext: 'jpg' }
145
+ it { should match('/pony正..jpg') .capturing file: 'pony正.', ext: 'jpg' }
146
+
147
+ it { should_not match('/.jpg') }
148
+ end
149
+
150
+ pattern '/:id/test.bar' do
151
+ it { should match('/3/test.bar') .capturing id: '3' }
152
+ it { should match('/2/test.bar') .capturing id: '2' }
153
+ it { should match('/2E/test.bar') .capturing id: '2E' }
154
+ it { should match('/2e/test.bar') .capturing id: '2e' }
155
+ it { should match('/%2E/test.bar') .capturing id: '%2E' }
156
+ end
157
+
158
+ pattern '/10/:id' do
159
+ it { should match('/10/test') .capturing id: 'test' }
160
+ it { should match('/10/te.st') .capturing id: 'te.st' }
161
+ end
162
+
163
+ pattern '/10.1/:id' do
164
+ it { should match('/10.1/test') .capturing id: 'test' }
165
+ it { should match('/10.1/te.st') .capturing id: 'te.st' }
166
+ end
167
+
168
+ pattern '/foo?' do
169
+ it { should match('/fo') }
170
+ it { should match('/foo') }
171
+ it { should_not match('') }
172
+ it { should_not match('/') }
173
+ it { should_not match('/f') }
174
+ it { should_not match('/fooo') }
175
+ end
176
+
177
+ pattern '/:fOO' do
178
+ it { should match('/a').capturing fOO: 'a' }
179
+ end
180
+
181
+ pattern '/:_X' do
182
+ it { should match('/a').capturing _X: 'a' }
183
+ end
184
+
185
+ pattern '/:f00' do
186
+ it { should match('/a').capturing f00: 'a' }
187
+ end
188
+
189
+ pattern '/:foo.?' do
190
+ it { should match('/a.').capturing foo: 'a.' }
191
+ it { should match('/xy').capturing foo: 'xy' }
192
+ end
193
+
194
+ pattern '/(a)' do
195
+ it { should match('/(a)') }
196
+ it { should_not match('/a') }
197
+ end
198
+
199
+ pattern '/:foo.?', greedy: false do
200
+ it { should match('/a.').capturing foo: 'a' }
201
+ it { should match('/xy').capturing foo: 'xy' }
202
+ end
203
+
204
+ pattern '/foo?', uri_decode: false do
205
+ it { should match('/foo') }
206
+ it { should match('/fo') }
207
+ it { should_not match('/foo?') }
208
+ end
209
+
210
+ pattern '/foo/bar', uri_decode: false do
211
+ it { should match('/foo/bar') }
212
+ it { should_not match('/foo%2Fbar') }
213
+ it { should_not match('/foo%2fbar') }
214
+ end
215
+
216
+ pattern "/path with spaces", uri_decode: false do
217
+ it { should match('/path with spaces') }
218
+ it { should_not match('/path%20with%20spaces') }
219
+ it { should_not match('/path%2Bwith%2Bspaces') }
220
+ it { should_not match('/path+with+spaces') }
221
+ end
222
+
223
+ pattern "/path with spaces", space_matches_plus: false do
224
+ it { should match('/path%20with%20spaces') }
225
+ it { should_not match('/path%2Bwith%2Bspaces') }
226
+ it { should_not match('/path+with+spaces') }
227
+ end
228
+
229
+ context 'error handling' do
230
+ example '? at beginning of route' do
231
+ expect { Mustermann::Simple.new('?foobar') }.
232
+ to raise_error(Mustermann::ParseError)
233
+ end
234
+
235
+ example 'invalid capture name' do
236
+ expect { Mustermann::Simple.new('/:1a/') }.
237
+ to raise_error(Mustermann::CompileError)
238
+ end
239
+ end
240
+
241
+ context "peeking" do
242
+ subject(:pattern) { Mustermann::Simple.new(":name") }
243
+
244
+ describe :peek_size do
245
+ example { pattern.peek_size("foo bar/blah") .should be == "foo bar".size }
246
+ example { pattern.peek_size("foo%20bar/blah") .should be == "foo%20bar".size }
247
+ example { pattern.peek_size("/foo bar") .should be_nil }
248
+ end
249
+
250
+ describe :peek_match do
251
+ example { pattern.peek_match("foo bar/blah") .to_s .should be == "foo bar" }
252
+ example { pattern.peek_match("foo%20bar/blah") .to_s .should be == "foo%20bar" }
253
+ example { pattern.peek_match("/foo bar") .should be_nil }
254
+ end
255
+
256
+ describe :peek_params do
257
+ example { pattern.peek_params("foo bar/blah") .should be == [{"name" => "foo bar"}, "foo bar".size] }
258
+ example { pattern.peek_params("foo%20bar/blah") .should be == [{"name" => "foo bar"}, "foo%20bar".size] }
259
+ example { pattern.peek_params("/foo bar") .should be_nil }
260
+ end
261
+ end
262
+
263
+ context "highlighting" do
264
+ let(:pattern) { Mustermann::Simple.new("/:name?/*") }
265
+ subject(:sexp) { Mustermann::Visualizer.highlight(pattern).to_sexp }
266
+ it { should be == "(root (separator /) (capture : (name name)) (optional ?) (separator /) (splat *))" }
267
+ end
268
+ end
metadata ADDED
@@ -0,0 +1,63 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: mustermann-simple
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.4.0
5
+ platform: ruby
6
+ authors:
7
+ - Konstantin Haase
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2014-11-25 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: mustermann
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - '='
18
+ - !ruby/object:Gem::Version
19
+ version: 0.4.0
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - '='
25
+ - !ruby/object:Gem::Version
26
+ version: 0.4.0
27
+ description: Adds Sinatra 1.x style patterns to Mustermman
28
+ email: konstantin.mailinglists@googlemail.com
29
+ executables: []
30
+ extensions: []
31
+ extra_rdoc_files: []
32
+ files:
33
+ - README.md
34
+ - lib/mustermann/simple.rb
35
+ - mustermann-simple.gemspec
36
+ - spec/simple_spec.rb
37
+ homepage: https://github.com/rkh/mustermann
38
+ licenses:
39
+ - MIT
40
+ metadata: {}
41
+ post_install_message:
42
+ rdoc_options: []
43
+ require_paths:
44
+ - lib
45
+ required_ruby_version: !ruby/object:Gem::Requirement
46
+ requirements:
47
+ - - ">="
48
+ - !ruby/object:Gem::Version
49
+ version: 2.1.0
50
+ required_rubygems_version: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - ">="
53
+ - !ruby/object:Gem::Version
54
+ version: '0'
55
+ requirements: []
56
+ rubyforge_project:
57
+ rubygems_version: 2.4.3
58
+ signing_key:
59
+ specification_version: 4
60
+ summary: Simple syntax for Mustermann
61
+ test_files:
62
+ - spec/simple_spec.rb
63
+ has_rdoc: