mustermann 1.0.1 → 1.0.2.rc1

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.
Files changed (126) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +18 -0
  3. data/.rspec +5 -0
  4. data/.travis.yml +25 -0
  5. data/.yardopts +3 -0
  6. data/Gemfile +7 -0
  7. data/README.md +230 -799
  8. data/Rakefile +27 -0
  9. data/mustermann-contrib/LICENSE +23 -0
  10. data/mustermann-contrib/README.md +1155 -0
  11. data/mustermann-contrib/examples/highlighting.rb +35 -0
  12. data/mustermann-contrib/highlighting.png +0 -0
  13. data/mustermann-contrib/irb.png +0 -0
  14. data/mustermann-contrib/lib/mustermann/cake.rb +19 -0
  15. data/mustermann-contrib/lib/mustermann/express.rb +38 -0
  16. data/mustermann-contrib/lib/mustermann/file_utils.rb +218 -0
  17. data/mustermann-contrib/lib/mustermann/file_utils/glob_pattern.rb +40 -0
  18. data/mustermann-contrib/lib/mustermann/fileutils.rb +1 -0
  19. data/mustermann-contrib/lib/mustermann/flask.rb +199 -0
  20. data/mustermann-contrib/lib/mustermann/pyramid.rb +29 -0
  21. data/mustermann-contrib/lib/mustermann/rails.rb +47 -0
  22. data/mustermann-contrib/lib/mustermann/shell.rb +57 -0
  23. data/mustermann-contrib/lib/mustermann/simple.rb +51 -0
  24. data/mustermann-contrib/lib/mustermann/string_scanner.rb +314 -0
  25. data/mustermann-contrib/lib/mustermann/strscan.rb +1 -0
  26. data/mustermann-contrib/lib/mustermann/template.rb +63 -0
  27. data/mustermann-contrib/lib/mustermann/uri_template.rb +1 -0
  28. data/mustermann-contrib/lib/mustermann/versions.rb +47 -0
  29. data/mustermann-contrib/lib/mustermann/visualizer.rb +39 -0
  30. data/mustermann-contrib/lib/mustermann/visualizer/highlight.rb +138 -0
  31. data/mustermann-contrib/lib/mustermann/visualizer/highlighter.rb +38 -0
  32. data/mustermann-contrib/lib/mustermann/visualizer/highlighter/ad_hoc.rb +95 -0
  33. data/mustermann-contrib/lib/mustermann/visualizer/highlighter/ast.rb +103 -0
  34. data/mustermann-contrib/lib/mustermann/visualizer/highlighter/composite.rb +46 -0
  35. data/mustermann-contrib/lib/mustermann/visualizer/highlighter/dummy.rb +19 -0
  36. data/mustermann-contrib/lib/mustermann/visualizer/highlighter/regular.rb +105 -0
  37. data/mustermann-contrib/lib/mustermann/visualizer/pattern_extension.rb +69 -0
  38. data/mustermann-contrib/lib/mustermann/visualizer/renderer/ansi.rb +24 -0
  39. data/mustermann-contrib/lib/mustermann/visualizer/renderer/generic.rb +47 -0
  40. data/mustermann-contrib/lib/mustermann/visualizer/renderer/hansi_template.rb +35 -0
  41. data/mustermann-contrib/lib/mustermann/visualizer/renderer/html.rb +51 -0
  42. data/mustermann-contrib/lib/mustermann/visualizer/renderer/sexp.rb +38 -0
  43. data/mustermann-contrib/lib/mustermann/visualizer/tree.rb +64 -0
  44. data/mustermann-contrib/lib/mustermann/visualizer/tree_renderer.rb +79 -0
  45. data/mustermann-contrib/mustermann-contrib.gemspec +19 -0
  46. data/mustermann-contrib/spec/cake_spec.rb +91 -0
  47. data/mustermann-contrib/spec/express_spec.rb +210 -0
  48. data/mustermann-contrib/spec/file_utils_spec.rb +120 -0
  49. data/mustermann-contrib/spec/flask_spec.rb +362 -0
  50. data/mustermann-contrib/spec/flask_subclass_spec.rb +369 -0
  51. data/mustermann-contrib/spec/pattern_extension_spec.rb +50 -0
  52. data/mustermann-contrib/spec/pyramid_spec.rb +102 -0
  53. data/mustermann-contrib/spec/rails_spec.rb +648 -0
  54. data/mustermann-contrib/spec/shell_spec.rb +148 -0
  55. data/mustermann-contrib/spec/simple_spec.rb +269 -0
  56. data/mustermann-contrib/spec/string_scanner_spec.rb +272 -0
  57. data/mustermann-contrib/spec/template_spec.rb +842 -0
  58. data/mustermann-contrib/spec/visualizer_spec.rb +199 -0
  59. data/mustermann-contrib/theme.png +0 -0
  60. data/mustermann-contrib/tree.png +0 -0
  61. data/mustermann/LICENSE +23 -0
  62. data/mustermann/README.md +853 -0
  63. data/{bench → mustermann/bench}/capturing.rb +0 -0
  64. data/{bench → mustermann/bench}/regexp.rb +0 -0
  65. data/{bench → mustermann/bench}/simple_vs_sinatra.rb +0 -0
  66. data/{bench → mustermann/bench}/template_vs_addressable.rb +0 -0
  67. data/{lib → mustermann/lib}/mustermann.rb +0 -0
  68. data/{lib → mustermann/lib}/mustermann/ast/boundaries.rb +0 -0
  69. data/{lib → mustermann/lib}/mustermann/ast/compiler.rb +0 -0
  70. data/{lib → mustermann/lib}/mustermann/ast/expander.rb +0 -0
  71. data/{lib → mustermann/lib}/mustermann/ast/node.rb +0 -0
  72. data/{lib → mustermann/lib}/mustermann/ast/param_scanner.rb +0 -0
  73. data/{lib → mustermann/lib}/mustermann/ast/parser.rb +0 -0
  74. data/{lib → mustermann/lib}/mustermann/ast/pattern.rb +0 -0
  75. data/{lib → mustermann/lib}/mustermann/ast/template_generator.rb +0 -0
  76. data/{lib → mustermann/lib}/mustermann/ast/transformer.rb +0 -0
  77. data/{lib → mustermann/lib}/mustermann/ast/translator.rb +0 -0
  78. data/{lib → mustermann/lib}/mustermann/ast/validation.rb +0 -0
  79. data/{lib → mustermann/lib}/mustermann/caster.rb +0 -0
  80. data/{lib → mustermann/lib}/mustermann/composite.rb +0 -0
  81. data/{lib → mustermann/lib}/mustermann/concat.rb +13 -2
  82. data/{lib → mustermann/lib}/mustermann/equality_map.rb +0 -0
  83. data/{lib → mustermann/lib}/mustermann/error.rb +0 -0
  84. data/{lib → mustermann/lib}/mustermann/expander.rb +0 -0
  85. data/{lib → mustermann/lib}/mustermann/extension.rb +0 -0
  86. data/{lib → mustermann/lib}/mustermann/identity.rb +0 -0
  87. data/{lib → mustermann/lib}/mustermann/mapper.rb +0 -0
  88. data/{lib → mustermann/lib}/mustermann/pattern.rb +1 -1
  89. data/{lib → mustermann/lib}/mustermann/pattern_cache.rb +0 -0
  90. data/{lib → mustermann/lib}/mustermann/regexp.rb +0 -0
  91. data/{lib → mustermann/lib}/mustermann/regexp_based.rb +0 -0
  92. data/{lib → mustermann/lib}/mustermann/regular.rb +0 -0
  93. data/{lib → mustermann/lib}/mustermann/simple_match.rb +0 -0
  94. data/{lib → mustermann/lib}/mustermann/sinatra.rb +1 -1
  95. data/{lib → mustermann/lib}/mustermann/sinatra/parser.rb +0 -0
  96. data/{lib → mustermann/lib}/mustermann/sinatra/safe_renderer.rb +0 -0
  97. data/{lib → mustermann/lib}/mustermann/sinatra/try_convert.rb +0 -0
  98. data/{lib → mustermann/lib}/mustermann/to_pattern.rb +0 -0
  99. data/{lib → mustermann/lib}/mustermann/version.rb +1 -1
  100. data/{mustermann.gemspec → mustermann/mustermann.gemspec} +0 -0
  101. data/{spec → mustermann/spec}/ast_spec.rb +0 -0
  102. data/{spec → mustermann/spec}/composite_spec.rb +0 -0
  103. data/{spec → mustermann/spec}/concat_spec.rb +12 -0
  104. data/{spec → mustermann/spec}/equality_map_spec.rb +0 -0
  105. data/{spec → mustermann/spec}/expander_spec.rb +0 -0
  106. data/{spec → mustermann/spec}/extension_spec.rb +0 -0
  107. data/{spec → mustermann/spec}/identity_spec.rb +0 -0
  108. data/{spec → mustermann/spec}/mapper_spec.rb +0 -0
  109. data/{spec → mustermann/spec}/mustermann_spec.rb +0 -0
  110. data/{spec → mustermann/spec}/pattern_spec.rb +0 -0
  111. data/{spec → mustermann/spec}/regexp_based_spec.rb +0 -0
  112. data/{spec → mustermann/spec}/regular_spec.rb +0 -0
  113. data/{spec → mustermann/spec}/simple_match_spec.rb +0 -0
  114. data/{spec → mustermann/spec}/sinatra_spec.rb +0 -0
  115. data/{spec → mustermann/spec}/to_pattern_spec.rb +0 -0
  116. data/support/lib/support.rb +7 -0
  117. data/support/lib/support/coverage.rb +23 -0
  118. data/support/lib/support/env.rb +19 -0
  119. data/support/lib/support/expand_matcher.rb +28 -0
  120. data/support/lib/support/generate_template_matcher.rb +27 -0
  121. data/support/lib/support/match_matcher.rb +39 -0
  122. data/support/lib/support/pattern.rb +42 -0
  123. data/support/lib/support/projects.rb +20 -0
  124. data/support/lib/support/scan_matcher.rb +63 -0
  125. data/support/support.gemspec +27 -0
  126. metadata +128 -58
@@ -0,0 +1,50 @@
1
+ # frozen_string_literal: true
2
+ require 'support'
3
+ require 'mustermann/visualizer'
4
+ require 'pp'
5
+ require 'stringio'
6
+
7
+ describe Mustermann::Visualizer::PatternExtension do
8
+ subject(:pattern) { Mustermann.new("/:name") }
9
+ before { Hansi.mode = 16 }
10
+ after { Hansi.mode = nil }
11
+
12
+ specify :to_ansi do
13
+ pattern.to_ansi(inspect: true, capture: :red, default: nil).should be == "\e[0m\"\e[0m/\e[0m\e[91m:\e[0m\e[91mname\e[0m\"\e[0m"
14
+ pattern.to_ansi(inspect: false, capture: :green, default: nil).should be == "\e[0m/\e[0m\e[32m:\e[0m\e[32mname\e[0m"
15
+ pattern.to_ansi(inspect: nil, capture: :green, default: nil).should be == "\e[0m/\e[0m\e[32m:\e[0m\e[32mname\e[0m"
16
+ end
17
+
18
+ specify :to_html do
19
+ pattern.to_html(css: false, class_prefix: "", tag: :tt).should be == '<tt class="pattern"><tt class="root"><tt class="separator">/</tt><tt class="capture">:<tt class="name">name</tt></tt></tt></tt>'
20
+ end
21
+
22
+ specify :to_tree do
23
+ pattern.to_tree.should be == Mustermann::Visualizer.tree(pattern).to_s
24
+ end
25
+
26
+ specify :color_inspect do
27
+ pattern.color_inspect.should include(pattern.to_ansi(inspect: true))
28
+ pattern.color_inspect.should include("#<Mustermann::Sinatra:")
29
+ end
30
+
31
+ specify :to_s do
32
+ object = Class.new { def puts(arg) arg.to_s end }.new
33
+ object.puts(pattern).should be == pattern.to_ansi
34
+ end
35
+
36
+ context :pretty_print do
37
+ before(:all) { ColorPrinter = Class.new(::PP) }
38
+ let(:output) { StringIO.new }
39
+
40
+ specify 'with color printer' do
41
+ ColorPrinter.new(output, 79).pp(pattern)
42
+ output.string.should be == pattern.color_inspect
43
+ end
44
+
45
+ specify 'without color printer' do
46
+ ::PP.new(output, 79).pp(pattern)
47
+ output.string.should be == pattern.inspect
48
+ end
49
+ end
50
+ end
@@ -0,0 +1,102 @@
1
+ # frozen_string_literal: true
2
+ require 'support'
3
+ require 'mustermann/pyramid'
4
+
5
+ describe Mustermann::Pyramid do
6
+ extend Support::Pattern
7
+
8
+ pattern '' do
9
+ it { should match('') }
10
+ it { should_not match('/') }
11
+
12
+ it { should expand.to('') }
13
+ it { should_not expand(a: 1) }
14
+
15
+ it { should generate_template('') }
16
+
17
+ it { should respond_to(:expand) }
18
+ it { should respond_to(:to_templates) }
19
+ end
20
+
21
+ pattern '/' do
22
+ it { should match('/') }
23
+ it { should_not match('/foo') }
24
+
25
+ it { should expand.to('/') }
26
+ it { should_not expand(a: 1) }
27
+ end
28
+
29
+ pattern '/foo' do
30
+ it { should match('/foo') }
31
+ it { should_not match('/bar') }
32
+ it { should_not match('/foo.bar') }
33
+
34
+ it { should expand.to('/foo') }
35
+ it { should_not expand(a: 1) }
36
+ end
37
+
38
+ pattern '/foo/bar' do
39
+ it { should match('/foo/bar') }
40
+ it { should_not match('/foo%2Fbar') }
41
+ it { should_not match('/foo%2fbar') }
42
+
43
+ it { should expand.to('/foo/bar') }
44
+ it { should_not expand(a: 1) }
45
+ end
46
+
47
+ pattern '/{foo}' do
48
+ it { should match('/foo') .capturing foo: 'foo' }
49
+ it { should match('/bar') .capturing foo: 'bar' }
50
+ it { should match('/foo.bar') .capturing foo: 'foo.bar' }
51
+ it { should match('/%0Afoo') .capturing foo: '%0Afoo' }
52
+ it { should match('/foo%2Fbar') .capturing foo: 'foo%2Fbar' }
53
+
54
+ it { should_not match('/foo?') }
55
+ it { should_not match('/foo/bar') }
56
+ it { should_not match('/') }
57
+ it { should_not match('/foo/') }
58
+
59
+ example { pattern.params('/foo') .should be == {"foo" => "foo"} }
60
+ example { pattern.params('/f%20o') .should be == {"foo" => "f o"} }
61
+ example { pattern.params('').should be_nil }
62
+
63
+ it { should expand(foo: 'bar') .to('/bar') }
64
+ it { should expand(foo: 'b r') .to('/b%20r') }
65
+ it { should expand(foo: 'foo/bar') .to('/foo%2Fbar') }
66
+
67
+ it { should_not expand(foo: 'foo', bar: 'bar') }
68
+ it { should_not expand(bar: 'bar') }
69
+ it { should_not expand }
70
+
71
+ it { should generate_template('/{foo}') }
72
+ end
73
+
74
+ pattern '/*foo' do
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
+ example { pattern.params("/foo/bar").should be == {"foo" => ["foo", "bar"]}}
86
+ it { should generate_template('/{+foo}') }
87
+ end
88
+
89
+ pattern '/{foo:.*}' do
90
+ it { should match('/') .capturing foo: '' }
91
+ it { should match('/foo') .capturing foo: 'foo' }
92
+ it { should match('/foo/bar') .capturing foo: 'foo/bar' }
93
+
94
+ it { should expand(foo: '') .to('/') }
95
+ it { should expand(foo: 'foo') .to('/foo') }
96
+ it { should expand(foo: 'foo/bar') .to('/foo/bar') }
97
+ it { should expand(foo: 'foo.bar') .to('/foo.bar') }
98
+
99
+ example { pattern.params("/foo/bar").should be == {"foo" => "foo/bar"}}
100
+ it { should generate_template('/{foo}') }
101
+ end
102
+ end
@@ -0,0 +1,648 @@
1
+ # frozen_string_literal: true
2
+ require 'support'
3
+ require 'mustermann/rails'
4
+
5
+ describe Mustermann::Rails do
6
+ extend Support::Pattern
7
+
8
+ pattern '' do
9
+ it { should match('') }
10
+ it { should_not match('/') }
11
+
12
+ it { should expand.to('') }
13
+ it { should_not expand(a: 1) }
14
+
15
+ it { should generate_template('') }
16
+
17
+ it { should respond_to(:expand) }
18
+ it { should respond_to(:to_templates) }
19
+ end
20
+
21
+ pattern '/' do
22
+ it { should match('/') }
23
+ it { should_not match('/foo') }
24
+
25
+ it { should expand.to('/') }
26
+ it { should_not expand(a: 1) }
27
+ end
28
+
29
+ pattern '/foo' do
30
+ it { should match('/foo') }
31
+ it { should_not match('/bar') }
32
+ it { should_not match('/foo.bar') }
33
+
34
+ it { should expand.to('/foo') }
35
+ it { should_not expand(a: 1) }
36
+ end
37
+
38
+ pattern '/foo/bar' do
39
+ it { should match('/foo/bar') }
40
+ it { should_not match('/foo%2Fbar') }
41
+ it { should_not match('/foo%2fbar') }
42
+
43
+ it { should expand.to('/foo/bar') }
44
+ it { should_not expand(a: 1) }
45
+ end
46
+
47
+ pattern '/:foo' do
48
+ it { should match('/foo') .capturing foo: 'foo' }
49
+ it { should match('/bar') .capturing foo: 'bar' }
50
+ it { should match('/foo.bar') .capturing foo: 'foo.bar' }
51
+ it { should match('/%0Afoo') .capturing foo: '%0Afoo' }
52
+ it { should match('/foo%2Fbar') .capturing foo: 'foo%2Fbar' }
53
+
54
+ it { should_not match('/foo?') }
55
+ it { should_not match('/foo/bar') }
56
+ it { should_not match('/') }
57
+ it { should_not match('/foo/') }
58
+
59
+ example { pattern.params('/foo') .should be == {"foo" => "foo"} }
60
+ example { pattern.params('/f%20o') .should be == {"foo" => "f o"} }
61
+ example { pattern.params('').should be_nil }
62
+
63
+ it { should expand(foo: 'bar') .to('/bar') }
64
+ it { should expand(foo: 'b r') .to('/b%20r') }
65
+ it { should expand(foo: 'foo/bar') .to('/foo%2Fbar') }
66
+
67
+ it { should_not expand(foo: 'foo', bar: 'bar') }
68
+ it { should_not expand(bar: 'bar') }
69
+ it { should_not expand }
70
+
71
+ it { should generate_template('/{foo}') }
72
+ end
73
+
74
+ pattern '/föö' do
75
+ it { should match("/f%C3%B6%C3%B6") }
76
+ it { should expand.to("/f%C3%B6%C3%B6") }
77
+ it { should_not expand(a: 1) }
78
+ end
79
+
80
+ pattern "/:foo/:bar" do
81
+ it { should match('/foo/bar') .capturing foo: 'foo', bar: 'bar' }
82
+ it { should match('/foo.bar/bar.foo') .capturing foo: 'foo.bar', bar: 'bar.foo' }
83
+ it { should match('/user@example.com/name') .capturing foo: 'user@example.com', bar: 'name' }
84
+ it { should match('/10.1/te.st') .capturing foo: '10.1', bar: 'te.st' }
85
+ it { should match('/10.1.2/te.st') .capturing foo: '10.1.2', bar: 'te.st' }
86
+
87
+ it { should_not match('/foo%2Fbar') }
88
+ it { should_not match('/foo%2fbar') }
89
+
90
+ example { pattern.params('/bar/foo').should be == {"foo" => "bar", "bar" => "foo"} }
91
+ example { pattern.params('').should be_nil }
92
+
93
+ it { should expand(foo: 'foo', bar: 'bar').to('/foo/bar') }
94
+ it { should_not expand(foo: 'foo') }
95
+ it { should_not expand(bar: 'bar') }
96
+
97
+ it { should generate_template('/{foo}/{bar}') }
98
+ end
99
+
100
+ pattern '/hello/:person' do
101
+ it { should match('/hello/Frank').capturing person: 'Frank' }
102
+ it { should expand(person: 'Frank') .to '/hello/Frank' }
103
+ it { should expand(person: 'Frank?') .to '/hello/Frank%3F' }
104
+
105
+ it { should generate_template('/hello/{person}') }
106
+ end
107
+
108
+ pattern '/?:foo?/?:bar?' do
109
+ it { should match('/?hello?/?world?').capturing foo: 'hello', bar: 'world' }
110
+ it { should_not match('/hello/world/') }
111
+ it { should expand(foo: 'hello', bar: 'world').to('/%3Fhello%3F/%3Fworld%3F') }
112
+ it { should generate_template('/?{foo}?/?{bar}?') }
113
+ end
114
+
115
+ pattern '/:foo_bar' do
116
+ it { should match('/hello').capturing foo_bar: 'hello' }
117
+ it { should expand(foo_bar: 'hello').to('/hello') }
118
+ it { should generate_template('/{foo_bar}') }
119
+ end
120
+
121
+ pattern '/*foo' do
122
+ it { should match('/') .capturing foo: '' }
123
+ it { should match('/foo') .capturing foo: 'foo' }
124
+ it { should match('/foo/bar') .capturing foo: 'foo/bar' }
125
+
126
+ it { should expand .to('/') }
127
+ it { should expand(foo: nil) .to('/') }
128
+ it { should expand(foo: '') .to('/') }
129
+ it { should expand(foo: 'foo') .to('/foo') }
130
+ it { should expand(foo: 'foo/bar') .to('/foo/bar') }
131
+ it { should expand(foo: 'foo.bar') .to('/foo.bar') }
132
+
133
+ it { should generate_template('/{+foo}') }
134
+ end
135
+
136
+ pattern '/*splat' do
137
+ it { should match('/') .capturing splat: '' }
138
+ it { should match('/foo') .capturing splat: 'foo' }
139
+ it { should match('/foo/bar') .capturing splat: 'foo/bar' }
140
+ it { should generate_template('/{+splat}') }
141
+ end
142
+
143
+ pattern '/:foo/*bar' do
144
+ it { should match("/foo/bar/baz") .capturing foo: 'foo', bar: 'bar/baz' }
145
+ it { should match("/foo%2Fbar/baz") .capturing foo: 'foo%2Fbar', bar: 'baz' }
146
+ it { should match("/foo/") .capturing foo: 'foo', bar: '' }
147
+ it { should match('/h%20w/h%20a%20y') .capturing foo: 'h%20w', bar: 'h%20a%20y' }
148
+ it { should_not match('/foo') }
149
+
150
+ it { should expand(foo: 'foo') .to('/foo/') }
151
+ it { should expand(foo: 'foo', bar: 'bar') .to('/foo/bar') }
152
+ it { should expand(foo: 'foo', bar: 'foo/bar') .to('/foo/foo/bar') }
153
+ it { should expand(foo: 'foo/bar', bar: 'bar') .to('/foo%2Fbar/bar') }
154
+
155
+ it { should generate_template('/{foo}/{+bar}') }
156
+ end
157
+
158
+ pattern '/test$/' do
159
+ it { should match('/test$/') }
160
+ it { should expand.to('/test$/') }
161
+ end
162
+
163
+ pattern '/te+st/' do
164
+ it { should match('/te+st/') }
165
+ it { should_not match('/test/') }
166
+ it { should_not match('/teest/') }
167
+ it { should expand.to('/te+st/') }
168
+ end
169
+
170
+ pattern "/path with spaces" do
171
+ it { should match('/path%20with%20spaces') }
172
+ it { should match('/path%2Bwith%2Bspaces') }
173
+ it { should match('/path+with+spaces') }
174
+ it { should expand.to('/path%20with%20spaces') }
175
+
176
+ it { should generate_template('/path%20with%20spaces') }
177
+ end
178
+
179
+ pattern '/foo&bar' do
180
+ it { should match('/foo&bar') }
181
+ end
182
+
183
+ pattern '/*a/:foo/*b/*c' do
184
+ it { should match('/bar/foo/bling/baz/boom').capturing a: 'bar', foo: 'foo', b: 'bling', c: 'baz/boom' }
185
+ example { pattern.params('/bar/foo/bling/baz/boom').should be == { "a" => 'bar', "foo" => 'foo', "b" => 'bling', "c" => 'baz/boom' } }
186
+ it { should expand(a: 'bar', foo: 'foo', b: 'bling', c: 'baz/boom').to('/bar/foo/bling/baz/boom') }
187
+ it { should generate_template('/{+a}/{foo}/{+b}/{+c}') }
188
+ end
189
+
190
+ pattern '/test.bar' do
191
+ it { should match('/test.bar') }
192
+ it { should_not match('/test0bar') }
193
+ end
194
+
195
+ pattern '/:file.:ext' do
196
+ it { should match('/pony.jpg') .capturing file: 'pony', ext: 'jpg' }
197
+ it { should match('/pony%2Ejpg') .capturing file: 'pony', ext: 'jpg' }
198
+ it { should match('/pony%2ejpg') .capturing file: 'pony', ext: 'jpg' }
199
+
200
+ it { should match('/pony%E6%AD%A3%2Ejpg') .capturing file: 'pony%E6%AD%A3', ext: 'jpg' }
201
+ it { should match('/pony%e6%ad%a3%2ejpg') .capturing file: 'pony%e6%ad%a3', ext: 'jpg' }
202
+ it { should match('/pony正%2Ejpg') .capturing file: 'pony正', ext: 'jpg' }
203
+ it { should match('/pony正%2ejpg') .capturing file: 'pony正', ext: 'jpg' }
204
+ it { should match('/pony正..jpg') .capturing file: 'pony正.', ext: 'jpg' }
205
+
206
+ it { should_not match('/.jpg') }
207
+ it { should expand(file: 'pony', ext: 'jpg').to('/pony.jpg') }
208
+ end
209
+
210
+ pattern '/:a(x)' do
211
+ it { should match('/a') .capturing a: 'a' }
212
+ it { should match('/xa') .capturing a: 'xa' }
213
+ it { should match('/axa') .capturing a: 'axa' }
214
+ it { should match('/ax') .capturing a: 'a' }
215
+ it { should match('/axax') .capturing a: 'axa' }
216
+ it { should match('/axaxx') .capturing a: 'axax' }
217
+ it { should expand(a: 'x').to('/xx') }
218
+ it { should expand(a: 'a').to('/ax') }
219
+
220
+ it { should generate_template('/{a}x') }
221
+ it { should generate_template('/{a}') }
222
+ end
223
+
224
+ pattern '/:user(@:host)' do
225
+ it { should match('/foo@bar') .capturing user: 'foo', host: 'bar' }
226
+ it { should match('/foo.foo@bar') .capturing user: 'foo.foo', host: 'bar' }
227
+ it { should match('/foo@bar.bar') .capturing user: 'foo', host: 'bar.bar' }
228
+
229
+ it { should expand(user: 'foo') .to('/foo') }
230
+ it { should expand(user: 'foo', host: 'bar') .to('/foo@bar') }
231
+
232
+ it { should generate_template('/{user}') }
233
+ it { should generate_template('/{user}@{host}') }
234
+ end
235
+
236
+ pattern '/:file(.:ext)' do
237
+ it { should match('/pony') .capturing file: 'pony', ext: nil }
238
+ it { should match('/pony.jpg') .capturing file: 'pony', ext: 'jpg' }
239
+ it { should match('/pony%2Ejpg') .capturing file: 'pony', ext: 'jpg' }
240
+ it { should match('/pony%2ejpg') .capturing file: 'pony', ext: 'jpg' }
241
+ it { should match('/pony.png.jpg') .capturing file: 'pony.png', ext: 'jpg' }
242
+ it { should match('/pony.') .capturing file: 'pony.' }
243
+ it { should_not match('/.jpg') }
244
+
245
+ it { should expand(file: 'pony') .to('/pony') }
246
+ it { should expand(file: 'pony', ext: 'jpg') .to('/pony.jpg') }
247
+
248
+ it { should generate_template('/{file}') }
249
+ it { should generate_template('/{file}.{ext}') }
250
+ end
251
+
252
+ pattern '/:id/test.bar' do
253
+ it { should match('/3/test.bar') .capturing id: '3' }
254
+ it { should match('/2/test.bar') .capturing id: '2' }
255
+ it { should match('/2E/test.bar') .capturing id: '2E' }
256
+ it { should match('/2e/test.bar') .capturing id: '2e' }
257
+ it { should match('/%2E/test.bar') .capturing id: '%2E' }
258
+ end
259
+
260
+ pattern '/10/:id' do
261
+ it { should match('/10/test') .capturing id: 'test' }
262
+ it { should match('/10/te.st') .capturing id: 'te.st' }
263
+ end
264
+
265
+ pattern '/10.1/:id' do
266
+ it { should match('/10.1/test') .capturing id: 'test' }
267
+ it { should match('/10.1/te.st') .capturing id: 'te.st' }
268
+ end
269
+
270
+ pattern '/:foo.:bar/:id' do
271
+ it { should match('/10.1/te.st') .capturing foo: "10", bar: "1", id: "te.st" }
272
+ it { should match('/10.1.2/te.st') .capturing foo: "10.1", bar: "2", id: "te.st" }
273
+ end
274
+
275
+ pattern '/:a/:b(.)(:c)' do
276
+ it { should match('/a/b') .capturing a: 'a', b: 'b', c: nil }
277
+ it { should match('/a/b.c') .capturing a: 'a', b: 'b', c: 'c' }
278
+ it { should match('/a.b/c') .capturing a: 'a.b', b: 'c', c: nil }
279
+ it { should match('/a.b/c.d') .capturing a: 'a.b', b: 'c', c: 'd' }
280
+ it { should_not match('/a.b/c.d/e') }
281
+
282
+ it { should expand(a: ?a, b: ?b) .to('/a/b.') }
283
+ it { should expand(a: ?a, b: ?b, c: ?c) .to('/a/b.c') }
284
+
285
+ it { should generate_template('/{a}/{b}') }
286
+ it { should generate_template('/{a}/{b}.') }
287
+ it { should generate_template('/{a}/{b}.{c}') }
288
+ end
289
+
290
+ pattern '/:a(foo:b)' do
291
+ it { should match('/barfoobar') .capturing a: 'bar', b: 'bar' }
292
+ it { should match('/barfoobarfoobar') .capturing a: 'barfoobar', b: 'bar' }
293
+ it { should match('/bar') .capturing a: 'bar', b: nil }
294
+ it { should_not match('/') }
295
+
296
+ it { should expand(a: ?a) .to('/a') }
297
+ it { should expand(a: ?a, b: ?b) .to('/afoob') }
298
+
299
+ it { should generate_template('/{a}foo{b}') }
300
+ it { should generate_template('/{a}') }
301
+ it { should_not generate_template('/{a}foo') }
302
+ end
303
+
304
+ pattern '/fo(o)' do
305
+ it { should match('/fo') }
306
+ it { should match('/foo') }
307
+ it { should_not match('') }
308
+ it { should_not match('/') }
309
+ it { should_not match('/f') }
310
+ it { should_not match('/fooo') }
311
+
312
+ it { should expand.to('/foo') }
313
+ end
314
+
315
+ pattern '/foo?' do
316
+ it { should match('/foo?') }
317
+ it { should_not match('/foo\?') }
318
+ it { should_not match('/fo') }
319
+ it { should_not match('/foo') }
320
+ it { should_not match('') }
321
+ it { should_not match('/') }
322
+ it { should_not match('/f') }
323
+ it { should_not match('/fooo') }
324
+
325
+ it { should expand.to('/foo%3F') }
326
+ end
327
+
328
+ pattern '/:fOO' do
329
+ it { should match('/a').capturing fOO: 'a' }
330
+ end
331
+
332
+ pattern '/:_X' do
333
+ it { should match('/a').capturing _X: 'a' }
334
+ end
335
+
336
+ pattern '/:f00' do
337
+ it { should match('/a').capturing f00: 'a' }
338
+ end
339
+
340
+ pattern '/:foo(/:bar)/:baz' do
341
+ it { should match('/foo/bar/baz').capturing foo: 'foo', bar: 'bar', baz: 'baz' }
342
+ it { should expand(foo: ?a, baz: ?b) .to('/a/b') }
343
+ it { should expand(foo: ?a, baz: ?b, bar: ?x) .to('/a/x/b') }
344
+ end
345
+
346
+ pattern '/:foo', capture: /\d+/ do
347
+ it { should match('/1') .capturing foo: '1' }
348
+ it { should match('/123') .capturing foo: '123' }
349
+
350
+ it { should_not match('/') }
351
+ it { should_not match('/foo') }
352
+ end
353
+
354
+ pattern '/:foo', capture: /\d+/ do
355
+ it { should match('/1') .capturing foo: '1' }
356
+ it { should match('/123') .capturing foo: '123' }
357
+
358
+ it { should_not match('/') }
359
+ it { should_not match('/foo') }
360
+ end
361
+
362
+ pattern '/:foo', capture: '1' do
363
+ it { should match('/1').capturing foo: '1' }
364
+
365
+ it { should_not match('/') }
366
+ it { should_not match('/foo') }
367
+ it { should_not match('/123') }
368
+ end
369
+
370
+ pattern '/:foo', capture: 'a.b' do
371
+ it { should match('/a.b') .capturing foo: 'a.b' }
372
+ it { should match('/a%2Eb') .capturing foo: 'a%2Eb' }
373
+ it { should match('/a%2eb') .capturing foo: 'a%2eb' }
374
+
375
+ it { should_not match('/ab') }
376
+ it { should_not match('/afb') }
377
+ it { should_not match('/a1b') }
378
+ it { should_not match('/a.bc') }
379
+ end
380
+
381
+ pattern '/:foo(/:bar)', capture: :alpha do
382
+ it { should match('/abc') .capturing foo: 'abc', bar: nil }
383
+ it { should match('/a/b') .capturing foo: 'a', bar: 'b' }
384
+ it { should match('/a') .capturing foo: 'a', bar: nil }
385
+
386
+ it { should_not match('/1/2') }
387
+ it { should_not match('/a/2') }
388
+ it { should_not match('/1/b') }
389
+ it { should_not match('/1') }
390
+ it { should_not match('/1/') }
391
+ it { should_not match('/a/') }
392
+ it { should_not match('//a') }
393
+ end
394
+
395
+ pattern '/:foo', capture: ['foo', 'bar', /\d+/] do
396
+ it { should match('/1') .capturing foo: '1' }
397
+ it { should match('/123') .capturing foo: '123' }
398
+ it { should match('/foo') .capturing foo: 'foo' }
399
+ it { should match('/bar') .capturing foo: 'bar' }
400
+
401
+ it { should_not match('/') }
402
+ it { should_not match('/baz') }
403
+ it { should_not match('/foo1') }
404
+ end
405
+
406
+ pattern '/:foo:bar:baz', capture: { foo: :alpha, bar: /\d+/ } do
407
+ it { should match('/ab123xy-1') .capturing foo: 'ab', bar: '123', baz: 'xy-1' }
408
+ it { should match('/ab123') .capturing foo: 'ab', bar: '12', baz: '3' }
409
+ it { should_not match('/123abcxy-1') }
410
+ it { should_not match('/abcxy-1') }
411
+ it { should_not match('/abc1') }
412
+ end
413
+
414
+ pattern '/:foo', capture: { foo: ['foo', 'bar', /\d+/] } do
415
+ it { should match('/1') .capturing foo: '1' }
416
+ it { should match('/123') .capturing foo: '123' }
417
+ it { should match('/foo') .capturing foo: 'foo' }
418
+ it { should match('/bar') .capturing foo: 'bar' }
419
+
420
+ it { should_not match('/') }
421
+ it { should_not match('/baz') }
422
+ it { should_not match('/foo1') }
423
+ end
424
+
425
+ pattern '/:file(.:ext)', capture: { ext: ['jpg', 'png'] } do
426
+ it { should match('/pony') .capturing file: 'pony', ext: nil }
427
+ it { should match('/pony.jpg') .capturing file: 'pony', ext: 'jpg' }
428
+ it { should match('/pony%2Ejpg') .capturing file: 'pony', ext: 'jpg' }
429
+ it { should match('/pony%2ejpg') .capturing file: 'pony', ext: 'jpg' }
430
+ it { should match('/pony.png') .capturing file: 'pony', ext: 'png' }
431
+ it { should match('/pony%2Epng') .capturing file: 'pony', ext: 'png' }
432
+ it { should match('/pony%2epng') .capturing file: 'pony', ext: 'png' }
433
+ it { should match('/pony.png.jpg') .capturing file: 'pony.png', ext: 'jpg' }
434
+ it { should match('/pony.jpg.png') .capturing file: 'pony.jpg', ext: 'png' }
435
+ it { should match('/pony.gif') .capturing file: 'pony.gif', ext: nil }
436
+ it { should match('/pony.') .capturing file: 'pony.', ext: nil }
437
+ it { should_not match('.jpg') }
438
+ end
439
+
440
+ pattern '/:file(:ext)', capture: { ext: ['.jpg', '.png', '.tar.gz'] } do
441
+ it { should match('/pony') .capturing file: 'pony', ext: nil }
442
+ it { should match('/pony.jpg') .capturing file: 'pony', ext: '.jpg' }
443
+ it { should match('/pony.png') .capturing file: 'pony', ext: '.png' }
444
+ it { should match('/pony.png.jpg') .capturing file: 'pony.png', ext: '.jpg' }
445
+ it { should match('/pony.jpg.png') .capturing file: 'pony.jpg', ext: '.png' }
446
+ it { should match('/pony.tar.gz') .capturing file: 'pony', ext: '.tar.gz' }
447
+ it { should match('/pony.gif') .capturing file: 'pony.gif', ext: nil }
448
+ it { should match('/pony.') .capturing file: 'pony.', ext: nil }
449
+ it { should_not match('/.jpg') }
450
+ end
451
+
452
+ pattern '/:a(@:b)', capture: { b: /\d+/ } do
453
+ it { should match('/a') .capturing a: 'a', b: nil }
454
+ it { should match('/a@1') .capturing a: 'a', b: '1' }
455
+ it { should match('/a@b') .capturing a: 'a@b', b: nil }
456
+ it { should match('/a@1@2') .capturing a: 'a@1', b: '2' }
457
+ end
458
+
459
+ pattern '/:a(b)', greedy: false do
460
+ it { should match('/ab').capturing a: 'a' }
461
+ end
462
+
463
+ pattern '/:file(.:ext)', greedy: false do
464
+ it { should match('/pony') .capturing file: 'pony', ext: nil }
465
+ it { should match('/pony.jpg') .capturing file: 'pony', ext: 'jpg' }
466
+ it { should match('/pony.png.jpg') .capturing file: 'pony', ext: 'png.jpg' }
467
+ end
468
+
469
+ pattern '/:controller(/:action(/:id(.:format)))' do
470
+ it { should match('/content').capturing controller: 'content' }
471
+ end
472
+
473
+ pattern '/fo(o)', uri_decode: false do
474
+ it { should match('/foo') }
475
+ it { should match('/fo') }
476
+ it { should_not match('/fo(o)') }
477
+ end
478
+
479
+ pattern '/foo/bar', uri_decode: false do
480
+ it { should match('/foo/bar') }
481
+ it { should_not match('/foo%2Fbar') }
482
+ it { should_not match('/foo%2fbar') }
483
+ end
484
+
485
+ pattern "/path with spaces", uri_decode: false do
486
+ it { should match('/path with spaces') }
487
+ it { should_not match('/path%20with%20spaces') }
488
+ it { should_not match('/path%2Bwith%2Bspaces') }
489
+ it { should_not match('/path+with+spaces') }
490
+ end
491
+
492
+ pattern "/path with spaces", space_matches_plus: false do
493
+ it { should match('/path%20with%20spaces') }
494
+ it { should_not match('/path%2Bwith%2Bspaces') }
495
+ it { should_not match('/path+with+spaces') }
496
+ end
497
+
498
+ context 'invalid syntax' do
499
+ example 'unexpected closing parenthesis' do
500
+ expect { Mustermann::Rails.new('foo)bar') }.
501
+ to raise_error(Mustermann::ParseError, 'unexpected ) while parsing "foo)bar"')
502
+ end
503
+
504
+ example 'missing closing parenthesis' do
505
+ expect { Mustermann::Rails.new('foo(bar') }.
506
+ to raise_error(Mustermann::ParseError, 'unexpected end of string while parsing "foo(bar"')
507
+ end
508
+ end
509
+
510
+ context 'invalid capture names' do
511
+ example 'empty name' do
512
+ expect { Mustermann::Rails.new('/:/') }.
513
+ to raise_error(Mustermann::CompileError, "capture name can't be empty: \"/:/\"")
514
+ end
515
+
516
+ example 'named splat' do
517
+ expect { Mustermann::Rails.new('/:splat/') }.
518
+ to raise_error(Mustermann::CompileError, "capture name can't be splat: \"/:splat/\"")
519
+ end
520
+
521
+ example 'named captures' do
522
+ expect { Mustermann::Rails.new('/:captures/') }.
523
+ to raise_error(Mustermann::CompileError, "capture name can't be captures: \"/:captures/\"")
524
+ end
525
+
526
+ example 'with capital letter' do
527
+ expect { Mustermann::Rails.new('/:Foo/') }.
528
+ to raise_error(Mustermann::CompileError, "capture name must start with underscore or lower case letter: \"/:Foo/\"")
529
+ end
530
+
531
+ example 'with integer' do
532
+ expect { Mustermann::Rails.new('/:1a/') }.
533
+ to raise_error(Mustermann::CompileError, "capture name must start with underscore or lower case letter: \"/:1a/\"")
534
+ end
535
+
536
+ example 'same name twice' do
537
+ expect { Mustermann::Rails.new('/:foo(/:bar)/:bar') }.
538
+ to raise_error(Mustermann::CompileError, "can't use the same capture name twice: \"/:foo(/:bar)/:bar\"")
539
+ end
540
+ end
541
+
542
+ context 'Regexp compatibility' do
543
+ describe :=== do
544
+ example('non-matching') { Mustermann::Rails.new("/") .should_not be === '/foo' }
545
+ example('matching') { Mustermann::Rails.new("/:foo") .should be === '/foo' }
546
+ end
547
+
548
+ describe :=~ do
549
+ example('non-matching') { Mustermann::Rails.new("/") .should_not be =~ '/foo' }
550
+ example('matching') { Mustermann::Rails.new("/:foo") .should be =~ '/foo' }
551
+
552
+ context 'String#=~' do
553
+ example('non-matching') { "/foo".should_not be =~ Mustermann::Rails.new("/") }
554
+ example('matching') { "/foo".should be =~ Mustermann::Rails.new("/:foo") }
555
+ end
556
+ end
557
+
558
+ describe :to_regexp do
559
+ example('empty pattern') { Mustermann::Rails.new('').to_regexp.should be == /\A(?-mix:)\Z/ }
560
+
561
+ context 'Regexp.try_convert' do
562
+ example('empty pattern') { Regexp.try_convert(Mustermann::Rails.new('')).should be == /\A(?-mix:)\Z/ }
563
+ end
564
+ end
565
+ end
566
+
567
+ context 'Proc compatibility' do
568
+ describe :to_proc do
569
+ example { Mustermann::Rails.new("/").to_proc.should be_a(Proc) }
570
+ example('non-matching') { Mustermann::Rails.new("/") .to_proc.call('/foo').should be == false }
571
+ example('matching') { Mustermann::Rails.new("/:foo") .to_proc.call('/foo').should be == true }
572
+ end
573
+ end
574
+
575
+ context "peeking" do
576
+ subject(:pattern) { Mustermann::Rails.new(":name") }
577
+
578
+ describe :peek_size do
579
+ example { pattern.peek_size("foo bar/blah") .should be == "foo bar".size }
580
+ example { pattern.peek_size("foo%20bar/blah") .should be == "foo%20bar".size }
581
+ example { pattern.peek_size("/foo bar") .should be_nil }
582
+ end
583
+
584
+ describe :peek_match do
585
+ example { pattern.peek_match("foo bar/blah") .to_s .should be == "foo bar" }
586
+ example { pattern.peek_match("foo%20bar/blah") .to_s .should be == "foo%20bar" }
587
+ example { pattern.peek_match("/foo bar") .should be_nil }
588
+ end
589
+
590
+ describe :peek_params do
591
+ example { pattern.peek_params("foo bar/blah") .should be == [{"name" => "foo bar"}, "foo bar".size] }
592
+ example { pattern.peek_params("foo%20bar/blah") .should be == [{"name" => "foo bar"}, "foo%20bar".size] }
593
+ example { pattern.peek_params("/foo bar") .should be_nil }
594
+ end
595
+ end
596
+
597
+ context 'version compatibility' do
598
+ context '2.3' do
599
+ pattern '(foo)', version: '2.3' do
600
+ it { should_not match("") }
601
+ it { should_not match("foo") }
602
+ it { should match("(foo)") }
603
+ end
604
+
605
+ pattern '\\:name', version: '2.3' do
606
+ it { should match('%5cfoo').capturing(name: 'foo') }
607
+ end
608
+ end
609
+
610
+ context '3.0' do
611
+ pattern '(foo)', version: '3.0' do
612
+ it { should match("") }
613
+ it { should match("foo") }
614
+ end
615
+
616
+ pattern '\\:name', version: '3.0' do
617
+ it { should match(':name') }
618
+ it { should_not match(':foo') }
619
+ end
620
+ end
621
+
622
+ context '3.2' do
623
+ pattern '\\:name', version: '3.2' do
624
+ it { should match('%5cfoo').capturing(name: 'foo') }
625
+ end
626
+ end
627
+
628
+ context '4.0' do
629
+ pattern '\\:name', version: '4.0' do
630
+ it { should match('foo').capturing(name: 'foo') }
631
+ end
632
+ end
633
+
634
+ context '4.2' do
635
+ pattern '\\:name', version: '4.2' do
636
+ it { should match(':name') }
637
+ it { should_not match(':foo') }
638
+ end
639
+ end
640
+
641
+ context '5.0' do
642
+ pattern 'foo|bar', version: '5.0' do
643
+ it { should match('foo') }
644
+ it { should match('bar') }
645
+ end
646
+ end
647
+ end
648
+ end