mustermann-express 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: 9c6795ad5822893caa59d2f3a3221c837d689fc3
4
+ data.tar.gz: 08d87ec97b358dc5ccb159f21e57b1559bd73dc6
5
+ SHA512:
6
+ metadata.gz: fd5a13bbba9b2701fbc0666d7329edd2f8aae9380ca9dac0e62936d14d439dc8b895b50c97a54da5d647683fbff2268a92f42e167d11e8044e04681d504c18ea
7
+ data.tar.gz: fce9833ea4809b93e30c75696763b8bf390e272b0e95115a3f6e671108d009726b57f955cd8229564a826b3d1e2a9a8cd465c3b7ba29e52583c1bc591271e40e
@@ -0,0 +1,96 @@
1
+ # Express Syntax for Mustermann
2
+
3
+ This gem implements the `express` pattern type for Mustermann. It is compatible with [Express](http://expressjs.com/) and [pillar.js](https://pillarjs.github.io/).
4
+
5
+ ## Overview
6
+
7
+ **Supported options:**
8
+ `capture`, `except`, `greedy`, `space_matches_plus`, `uri_decode`, and `ignore_unknown_options`.
9
+
10
+ **External documentation:**
11
+ [path-to-regexp](https://github.com/pillarjs/path-to-regexp#path-to-regexp),
12
+ [live demo](http://forbeslindesay.github.io/express-route-tester/)
13
+
14
+ Express patterns feature named captures (with repetition support via suffixes) that start with a colon and can have an optional regular expression constraint or unnamed captures that require a constraint.
15
+
16
+ ``` ruby
17
+ require 'mustermann/express'
18
+
19
+ Mustermann.new('/:name/:rest+', type: :express).params('/a/b/c') # => { name: 'a', rest: 'b/c' }
20
+
21
+ pattern = Mustermann.new('/:name', type: :express)
22
+
23
+ pattern.respond_to? :expand # => true
24
+ pattern.expand(name: 'foo') # => '/foo'
25
+
26
+ pattern.respond_to? :to_templates # => true
27
+ pattern.to_templates # => ['/{name}']
28
+ ```
29
+
30
+ ## Syntax
31
+
32
+ <table>
33
+ <thead>
34
+ <tr>
35
+ <th>Syntax Element</th>
36
+ <th>Description</th>
37
+ </tr>
38
+ </thead>
39
+ <tbody>
40
+ <tr>
41
+ <td><b>:</b><i>name</i></td>
42
+ <td>
43
+ Captures anything but a forward slash in a semi-greedy fashion. Capture is named <i>name</i>.
44
+ Capture behavior can be modified with <tt>capture</tt> and <tt>greedy</tt> option.
45
+ </td>
46
+ </tr>
47
+ <tr>
48
+ <td><b>:</b><i>name</i><b>+</b></td>
49
+ <td>
50
+ Captures one or more segments (with segments being separated by forward slashes).
51
+ Capture is named <i>name</i>.
52
+ Capture behavior can be modified with <tt>capture</tt> option.
53
+ </td>
54
+ </tr>
55
+ <tr>
56
+ <td><b>:</b><i>name</i><b>*</b></td>
57
+ <td>
58
+ Captures zero or more segments (with segments being separated by forward slashes).
59
+ Capture is named <i>name</i>.
60
+ Capture behavior can be modified with <tt>capture</tt> option.
61
+ </td>
62
+ </tr>
63
+ <tr>
64
+ <td><b>:</b><i>name</i><b>?</b></td>
65
+ <td>
66
+ Captures anything but a forward slash in a semi-greedy fashion. Capture is named <i>name</i>.
67
+ Also matches an empty string.
68
+ Capture behavior can be modified with <tt>capture</tt> and <tt>greedy</tt> option.
69
+ </td>
70
+ </tr>
71
+ <tr>
72
+ <td><b>:</b><i>name</i><b>(</b><i>regexp</i><b>)</b></td>
73
+ <td>
74
+ Captures anything matching the <i>regexp</i> regular expression. Capture is named <i>name</i>.
75
+ Capture behavior can be modified with <tt>capture</tt>.
76
+ </td>
77
+ </tr>
78
+ <tr>
79
+ <td><b>(</b><i>regexp</i><b>)</b></td>
80
+ <td>
81
+ Captures anything matching the <i>regexp</i> regular expression. Capture is named splat.
82
+ Capture behavior can be modified with <tt>capture</tt>.
83
+ </td>
84
+ </tr>
85
+ <tr>
86
+ <td><b>/</b></td>
87
+ <td>
88
+ Matches forward slash. Does not match URI encoded version of forward slash.
89
+ </td>
90
+ </tr>
91
+ <tr>
92
+ <td><i>any other character</i></td>
93
+ <td>Matches exactly that character or a URI encoded version of it.</td>
94
+ </tr>
95
+ </tbody>
96
+ </table>
@@ -0,0 +1,37 @@
1
+ require 'mustermann'
2
+ require 'mustermann/ast/pattern'
3
+
4
+ module Mustermann
5
+ # Express style pattern implementation.
6
+ #
7
+ # @example
8
+ # Mustermann.new('/:foo', type: :express) === '/bar' # => true
9
+ #
10
+ # @see Mustermann::Pattern
11
+ # @see file:README.md#flask Syntax description in the README
12
+ class Express < AST::Pattern
13
+ register :express
14
+
15
+ on(nil, ??, ?+, ?*, ?)) { |c| unexpected(c) }
16
+ on(?:) { |c| node(:capture) { scan(/\w+/) } }
17
+ on(?() { |c| node(:splat, constraint: read_brackets(?(, ?))) }
18
+
19
+ suffix ??, after: :capture do |char, element|
20
+ unexpected(char) unless element.is_a? :capture
21
+ node(:optional, element)
22
+ end
23
+
24
+ suffix ?*, after: :capture do |match, element|
25
+ node(:named_splat, element.name)
26
+ end
27
+
28
+ suffix ?+, after: :capture do |match, element|
29
+ node(:named_splat, element.name, constraint: ".+")
30
+ end
31
+
32
+ suffix ?(, after: :capture do |match, element|
33
+ element.constraint = read_brackets(?(, ?))
34
+ element
35
+ end
36
+ end
37
+ 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-express"
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{Express.js syntax for Mustermann}
11
+ s.description = %q{Adds express.js 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,209 @@
1
+ require 'support'
2
+ require 'mustermann/express'
3
+
4
+ describe Mustermann::Express do
5
+ extend Support::Pattern
6
+
7
+ pattern '' do
8
+ it { should match('') }
9
+ it { should_not match('/') }
10
+
11
+ it { should expand.to('') }
12
+ it { should_not expand(a: 1) }
13
+
14
+ it { should generate_template('') }
15
+
16
+ it { should respond_to(:expand) }
17
+ it { should respond_to(:to_templates) }
18
+ end
19
+
20
+ pattern '/' do
21
+ it { should match('/') }
22
+ it { should_not match('/foo') }
23
+
24
+ it { should expand.to('/') }
25
+ it { should_not expand(a: 1) }
26
+ end
27
+
28
+ pattern '/foo' do
29
+ it { should match('/foo') }
30
+ it { should_not match('/bar') }
31
+ it { should_not match('/foo.bar') }
32
+
33
+ it { should expand.to('/foo') }
34
+ it { should_not expand(a: 1) }
35
+ end
36
+
37
+ pattern '/foo/bar' do
38
+ it { should match('/foo/bar') }
39
+ it { should_not match('/foo%2Fbar') }
40
+ it { should_not match('/foo%2fbar') }
41
+
42
+ it { should expand.to('/foo/bar') }
43
+ it { should_not expand(a: 1) }
44
+ end
45
+
46
+ pattern '/:foo' do
47
+ it { should match('/foo') .capturing foo: 'foo' }
48
+ it { should match('/bar') .capturing foo: 'bar' }
49
+ it { should match('/foo.bar') .capturing foo: 'foo.bar' }
50
+ it { should match('/%0Afoo') .capturing foo: '%0Afoo' }
51
+ it { should match('/foo%2Fbar') .capturing foo: 'foo%2Fbar' }
52
+
53
+ it { should_not match('/foo?') }
54
+ it { should_not match('/foo/bar') }
55
+ it { should_not match('/') }
56
+ it { should_not match('/foo/') }
57
+
58
+ example { pattern.params('/foo') .should be == {"foo" => "foo"} }
59
+ example { pattern.params('/f%20o') .should be == {"foo" => "f o"} }
60
+ example { pattern.params('').should be_nil }
61
+
62
+ it { should expand(foo: 'bar') .to('/bar') }
63
+ it { should expand(foo: 'b r') .to('/b%20r') }
64
+ it { should expand(foo: 'foo/bar') .to('/foo%2Fbar') }
65
+
66
+ it { should_not expand(foo: 'foo', bar: 'bar') }
67
+ it { should_not expand(bar: 'bar') }
68
+ it { should_not expand }
69
+
70
+ it { should generate_template('/{foo}') }
71
+ end
72
+
73
+ pattern '/:foo+' do
74
+ it { should_not match('/') }
75
+ it { should match('/foo') .capturing foo: 'foo' }
76
+ it { should match('/foo/bar') .capturing foo: 'foo/bar' }
77
+
78
+ it { should expand .to('/') }
79
+ it { should expand(foo: nil) .to('/') }
80
+ it { should expand(foo: '') .to('/') }
81
+ it { should expand(foo: 'foo') .to('/foo') }
82
+ it { should expand(foo: 'foo/bar') .to('/foo/bar') }
83
+ it { should expand(foo: 'foo.bar') .to('/foo.bar') }
84
+
85
+ it { should generate_template('/{+foo}') }
86
+ end
87
+
88
+ pattern '/:foo?' do
89
+ it { should match('/foo') .capturing foo: 'foo' }
90
+ it { should match('/bar') .capturing foo: 'bar' }
91
+ it { should match('/foo.bar') .capturing foo: 'foo.bar' }
92
+ it { should match('/%0Afoo') .capturing foo: '%0Afoo' }
93
+ it { should match('/foo%2Fbar') .capturing foo: 'foo%2Fbar' }
94
+ it { should match('/') }
95
+
96
+ it { should_not match('/foo?') }
97
+ it { should_not match('/foo/bar') }
98
+ it { should_not match('/foo/') }
99
+
100
+ example { pattern.params('/foo') .should be == {"foo" => "foo"} }
101
+ example { pattern.params('/f%20o') .should be == {"foo" => "f o"} }
102
+ example { pattern.params('/') .should be == {"foo" => nil } }
103
+
104
+ it { should expand(foo: 'bar') .to('/bar') }
105
+ it { should expand(foo: 'b r') .to('/b%20r') }
106
+ it { should expand(foo: 'foo/bar') .to('/foo%2Fbar') }
107
+ it { should expand .to('/') }
108
+
109
+ it { should_not expand(foo: 'foo', bar: 'bar') }
110
+ it { should_not expand(bar: 'bar') }
111
+
112
+ it { should generate_template('/{foo}') }
113
+ it { should generate_template('/') }
114
+ end
115
+
116
+ pattern '/:foo*' do
117
+ it { should match('/') .capturing foo: '' }
118
+ it { should match('/foo') .capturing foo: 'foo' }
119
+ it { should match('/foo/bar') .capturing foo: 'foo/bar' }
120
+
121
+ it { should expand .to('/') }
122
+ it { should expand(foo: nil) .to('/') }
123
+ it { should expand(foo: '') .to('/') }
124
+ it { should expand(foo: 'foo') .to('/foo') }
125
+ it { should expand(foo: 'foo/bar') .to('/foo/bar') }
126
+ it { should expand(foo: 'foo.bar') .to('/foo.bar') }
127
+
128
+ it { should generate_template('/{+foo}') }
129
+ end
130
+
131
+ pattern '/:foo(.*)' do
132
+ it { should match('/') .capturing foo: '' }
133
+ it { should match('/foo') .capturing foo: 'foo' }
134
+ it { should match('/foo/bar') .capturing foo: 'foo/bar' }
135
+
136
+ it { should expand(foo: '') .to('/') }
137
+ it { should expand(foo: 'foo') .to('/foo') }
138
+ it { should expand(foo: 'foo/bar') .to('/foo/bar') }
139
+ it { should expand(foo: 'foo.bar') .to('/foo.bar') }
140
+
141
+ it { should generate_template('/{foo}') }
142
+ end
143
+
144
+ pattern '/:foo(\d+)' do
145
+ it { should_not match('/') }
146
+ it { should_not match('/foo') }
147
+ it { should match('/15') .capturing foo: '15' }
148
+ it { should generate_template('/{foo}') }
149
+ end
150
+
151
+ pattern '/:foo(\d+|bar)' do
152
+ it { should_not match('/') }
153
+ it { should_not match('/foo') }
154
+ it { should match('/15') .capturing foo: '15' }
155
+ it { should match('/bar') .capturing foo: 'bar' }
156
+ it { should generate_template('/{foo}') }
157
+ end
158
+
159
+ pattern '/:foo(\))' do
160
+ it { should_not match('/') }
161
+ it { should_not match('/foo') }
162
+ it { should match('/)').capturing foo: ')' }
163
+ it { should generate_template('/{foo}') }
164
+ end
165
+
166
+ pattern '/:foo(prefix(\d+|bar))' do
167
+ it { should_not match('/prefix') }
168
+ it { should_not match('/prefixfoo') }
169
+ it { should match('/prefix15') .capturing foo: 'prefix15' }
170
+ it { should match('/prefixbar') .capturing foo: 'prefixbar' }
171
+ it { should generate_template('/{foo}') }
172
+ end
173
+
174
+ pattern '/(.+)' do
175
+ it { should_not match('/') }
176
+ it { should match('/foo') .capturing splat: 'foo' }
177
+ it { should match('/foo/bar') .capturing splat: 'foo/bar' }
178
+ it { should generate_template('/{+splat}') }
179
+ end
180
+
181
+ pattern '/(foo(a|b))' do
182
+ it { should_not match('/') }
183
+ it { should match('/fooa') .capturing splat: 'fooa' }
184
+ it { should match('/foob') .capturing splat: 'foob' }
185
+ it { should generate_template('/{+splat}') }
186
+ end
187
+
188
+ context 'invalid syntax' do
189
+ example 'unexpected closing parenthesis' do
190
+ expect { Mustermann::Express.new('foo)bar') }.
191
+ to raise_error(Mustermann::ParseError, 'unexpected ) while parsing "foo)bar"')
192
+ end
193
+
194
+ example 'missing closing parenthesis' do
195
+ expect { Mustermann::Express.new('foo(bar') }.
196
+ to raise_error(Mustermann::ParseError, 'unexpected end of string while parsing "foo(bar"')
197
+ end
198
+
199
+ example 'unexpected ?' do
200
+ expect { Mustermann::Express.new('foo?bar') }.
201
+ to raise_error(Mustermann::ParseError, 'unexpected ? while parsing "foo?bar"')
202
+ end
203
+
204
+ example 'unexpected *' do
205
+ expect { Mustermann::Express.new('foo*bar') }.
206
+ to raise_error(Mustermann::ParseError, 'unexpected * while parsing "foo*bar"')
207
+ end
208
+ end
209
+ end
metadata ADDED
@@ -0,0 +1,63 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: mustermann-express
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 express.js 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/express.rb
35
+ - mustermann-express.gemspec
36
+ - spec/express_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: Express.js syntax for Mustermann
61
+ test_files:
62
+ - spec/express_spec.rb
63
+ has_rdoc: