mustermann19 0.3.1.2 → 0.4.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,156 @@
1
+ require 'mustermann/ast/translator'
2
+
3
+ module Mustermann
4
+ # @see Mustermann::AST::Pattern
5
+ module AST
6
+ # Regexp compilation logic.
7
+ # @!visibility private
8
+ class Compiler < Translator
9
+ raises CompileError
10
+
11
+ # Trivial compilations
12
+ translate(Array) { |**o| map { |e| t(e, **o) }.join }
13
+ translate(:node) { |**o| t(payload, **o) }
14
+ translate(:separator) { |**o| Regexp.escape(payload) }
15
+ translate(:optional) { |**o| "(?:%s)?" % t(payload, **o) }
16
+ translate(:char) { |**o| t.encoded(payload, **o) }
17
+
18
+ translate :union do |**options|
19
+ "(?:%s)" % payload.map { |e| "(?:%s)" % t(e, **options) }.join(?|)
20
+ end
21
+
22
+ translate :expression do |greedy: true, **options|
23
+ t(payload, allow_reserved: operator.allow_reserved, greedy: greedy && !operator.allow_reserved,
24
+ parametric: operator.parametric, separator: operator.separator, **options)
25
+ end
26
+
27
+ translate :with_look_ahead do |**options|
28
+ lookahead = each_leaf.inject("") do |ahead, element|
29
+ ahead + t(element, skip_optional: true, lookahead: ahead, greedy: false, no_captures: true, **options).to_s
30
+ end
31
+ lookahead << (at_end ? '$' : '/')
32
+ t(head, lookahead: lookahead, **options) + t(payload, **options)
33
+ end
34
+
35
+ # Capture compilation is complex. :(
36
+ # @!visibility private
37
+ class Capture < NodeTranslator
38
+ register :capture
39
+
40
+ # @!visibility private
41
+ def translate(**options)
42
+ return pattern(options) if options[:no_captures]
43
+ "(?<#{name}>#{translate(no_captures: true, **options)})"
44
+ end
45
+
46
+ # @return [String] regexp without the named capture
47
+ # @!visibility private
48
+ def pattern(capture: nil, **options)
49
+ case capture
50
+ when Symbol then from_symbol(capture, **options)
51
+ when Array then from_array(capture, **options)
52
+ when Hash then from_hash(capture, **options)
53
+ when String then from_string(capture, **options)
54
+ when nil then from_nil(**options)
55
+ else capture
56
+ end
57
+ end
58
+
59
+ private
60
+ def qualified(string, greedy: true, **options) "#{string}#{qualifier || "+#{?? unless greedy}"}" end
61
+ def with_lookahead(string, lookahead: nil, **options) lookahead ? "(?:(?!#{lookahead})#{string})" : string end
62
+ def from_hash(hash, **options) pattern(capture: hash[name.to_sym], **options) end
63
+ def from_array(array, **options) Regexp.union(*array.map { |e| pattern(capture: e, **options) }) end
64
+ def from_symbol(symbol, **options) qualified(with_lookahead("[[:#{symbol}:]]", **options), **options) end
65
+ def from_string(string, **options) Regexp.new(string.chars.map { |c| t.encoded(c, **options) }.join) end
66
+ def from_nil(**options) qualified(with_lookahead(default(**options), **options), **options) end
67
+ def default(**options) constraint || "[^/\\?#]" end
68
+ end
69
+
70
+ # @!visibility private
71
+ class Splat < Capture
72
+ register :splat, :named_splat
73
+ # splats are always non-greedy
74
+ # @!visibility private
75
+ def pattern(**options)
76
+ constraint || ".*?"
77
+ end
78
+ end
79
+
80
+ # @!visibility private
81
+ class Variable < Capture
82
+ register :variable
83
+
84
+ # @!visibility private
85
+ def translate(**options)
86
+ return super(**options) if explode or not options[:parametric]
87
+ parametric super(parametric: false, **options)
88
+ end
89
+
90
+ # @!visibility private
91
+ def pattern(parametric: false, separator: nil, **options)
92
+ register_param(parametric: parametric, separator: separator, **options)
93
+ pattern = super(**options)
94
+ pattern = parametric(pattern) if parametric
95
+ pattern = "#{pattern}(?:#{Regexp.escape(separator)}#{pattern})*" if explode and separator
96
+ pattern
97
+ end
98
+
99
+ # @!visibility private
100
+ def parametric(string)
101
+ "#{Regexp.escape(name)}(?:=#{string})?"
102
+ end
103
+
104
+ # @!visibility private
105
+ def qualified(string, **options)
106
+ prefix ? "#{string}{1,#{prefix}}" : super(string, **options)
107
+ end
108
+
109
+ # @!visibility private
110
+ def default(allow_reserved: false, **options)
111
+ allow_reserved ? '[\w\-\.~%\:/\?#\[\]@\!\$\&\'\(\)\*\+,;=]' : '[\w\-\.~%]'
112
+ end
113
+
114
+ # @!visibility private
115
+ def register_param(parametric: false, split_params: nil, separator: nil, **options)
116
+ return unless explode and split_params
117
+ split_params[name] = { separator: separator, parametric: parametric }
118
+ end
119
+ end
120
+
121
+ # @return [String] Regular expression for matching the given character in all representations
122
+ # @!visibility private
123
+ def encoded(char, uri_decode: true, space_matches_plus: true, **options)
124
+ return Regexp.escape(char) unless uri_decode
125
+ encoded = escape(char, escape: /./)
126
+ list = [escape(char), encoded.downcase, encoded.upcase].uniq.map { |c| Regexp.escape(c) }
127
+ if char == " "
128
+ list << encoded('+') if space_matches_plus
129
+ list << " "
130
+ end
131
+ "(?:%s)" % list.join("|")
132
+ end
133
+
134
+ # Compiles an AST to a regular expression.
135
+ # @param [Mustermann::AST::Node] ast the tree
136
+ # @return [Regexp] corresponding regular expression.
137
+ #
138
+ # @!visibility private
139
+ def self.compile(ast, **options)
140
+ new.compile(ast, **options)
141
+ end
142
+
143
+ # Compiles an AST to a regular expression.
144
+ # @param [Mustermann::AST::Node] ast the tree
145
+ # @return [Regexp] corresponding regular expression.
146
+ #
147
+ # @!visibility private
148
+ def compile(ast, except: nil, **options)
149
+ except &&= "(?!#{translate(except, no_captures: true, **options)}\\Z)"
150
+ Regexp.new("#{except}#{translate(ast, **options)}")
151
+ end
152
+ end
153
+
154
+ private_constant :Compiler
155
+ end
156
+ end
data/spec/ast_spec.rb ADDED
@@ -0,0 +1,14 @@
1
+ require 'support'
2
+ require 'mustermann/ast/node'
3
+
4
+ describe Mustermann::AST do
5
+ describe :type do
6
+ example { Mustermann::AST::Node[:char].type .should be == :char }
7
+ example { Mustermann::AST::Node[:char].new.type .should be == :char }
8
+ end
9
+
10
+ describe :min_size do
11
+ example { Mustermann::AST::Node[:char].new.min_size.should be == 1 }
12
+ example { Mustermann::AST::Node[:node].new.min_size.should be == 0 }
13
+ end
14
+ end
@@ -15,7 +15,7 @@ describe Mustermann do
15
15
  example { Mustermann.new('', type: :template) .should be_a(Mustermann::Template) }
16
16
 
17
17
  example { expect { Mustermann.new('', foo: :bar) }.to raise_error(ArgumentError, "unsupported option :foo for Mustermann::Sinatra") }
18
- example { expect { Mustermann.new('', type: :ast) }.to raise_error(ArgumentError, "unsupported type :ast") }
18
+ example { expect { Mustermann.new('', type: :ast) }.to raise_error(ArgumentError, /unsupported type :ast/) }
19
19
  end
20
20
 
21
21
  context "pattern argument" do
@@ -59,7 +59,8 @@ describe Mustermann do
59
59
  example { Mustermann[:simple] .should be == Mustermann::Simple }
60
60
  example { Mustermann[:template] .should be == Mustermann::Template }
61
61
 
62
- example { expect { Mustermann[:ast] }.to raise_error(ArgumentError, "unsupported type :ast") }
62
+ example { expect { Mustermann[:ast] }.to raise_error(ArgumentError, /unsupported type :ast/) }
63
+ example { expect { Mustermann[:expander] }.to raise_error(ArgumentError, "unsupported type :expander") }
63
64
  end
64
65
 
65
66
  describe :extend_object do
data/spec/rails_spec.rb CHANGED
@@ -593,4 +593,49 @@ describe Mustermann::Rails do
593
593
  example { pattern.peek_params("/foo bar") .should be_nil }
594
594
  end
595
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
+ end
596
641
  end
data/spec/shell_spec.rb CHANGED
@@ -59,8 +59,10 @@ describe Mustermann::Shell do
59
59
  it { should_not match('/foo/') }
60
60
  end
61
61
 
62
- pattern '/föö' do
63
- it { should match("/f%C3%B6%C3%B6") }
62
+ unless defined?(JRUBY_VERSION)
63
+ pattern '/föö' do
64
+ it { should match("/f%C3%B6%C3%B6") }
65
+ end
64
66
  end
65
67
 
66
68
  pattern '/test$/' do
data/spec/sinatra_spec.rb CHANGED
@@ -464,6 +464,18 @@ describe Mustermann::Sinatra do
464
464
  it { should_not expand(a: 'foo', b: 'bar', c: 'baz') }
465
465
  end
466
466
 
467
+ pattern "/:a/:b|:c" do
468
+ it { should match("foo") .capturing c: 'foo' }
469
+ it { should match("/foo/bar") .capturing a: 'foo', b: 'bar' }
470
+
471
+ it { should generate_template('/{a}/{b}') }
472
+ it { should generate_template('{c}') }
473
+
474
+ it { should expand(a: 'foo', b: 'bar') .to('/foo/bar') }
475
+ it { should expand(c: 'foo') .to('foo') }
476
+ it { should_not expand(a: 'foo', b: 'bar', c: 'baz') }
477
+ end
478
+
467
479
  pattern '/:foo', capture: /\d+/ do
468
480
  it { should match('/1') .capturing foo: '1' }
469
481
  it { should match('/123') .capturing foo: '123' }
@@ -649,11 +661,6 @@ describe Mustermann::Sinatra do
649
661
  to raise_error(Mustermann::ParseError, 'unexpected ? while parsing "foo??bar"')
650
662
  end
651
663
 
652
- example '| outside of group' do
653
- expect { Mustermann::Sinatra.new('foo|bar') }.
654
- to raise_error(Mustermann::ParseError, 'unexpected | while parsing "foo|bar"')
655
- end
656
-
657
664
  example 'dangling escape' do
658
665
  expect { Mustermann::Sinatra.new('foo\\') }.
659
666
  to raise_error(Mustermann::ParseError, 'unexpected end of string while parsing "foo\\\\"')
@@ -36,4 +36,4 @@ RSpec::Matchers.define :match do |expected|
36
36
  actual.to_s, expected
37
37
  ]
38
38
  end
39
- end
39
+ end
@@ -31,7 +31,7 @@ module Support
31
31
  end
32
32
 
33
33
  def subject_for(pattern, *args)
34
- instance = Timeout.timeout(1) { described_class.new(pattern, *args) }
34
+ instance = Timeout.timeout(defined?(JRUBY_VERSION) ? 3 : 1) { described_class.new(pattern, *args) }
35
35
  proc { instance }
36
36
  rescue Timeout::Error => error
37
37
  proc { raise Timeout::Error, "could not compile #{pattern.inspect} in time", error.backtrace }
@@ -39,4 +39,4 @@ module Support
39
39
  proc { raise error }
40
40
  end
41
41
  end
42
- end
42
+ end
@@ -561,6 +561,11 @@ describe Mustermann::Template do
561
561
  end
562
562
 
563
563
  context 'operator +' do
564
+ pattern '/{a}/{+b}' do
565
+ it { should match('/foo/bar/baz').capturing(a: 'foo', b: 'bar/baz') }
566
+ it { should expand(a: 'foo/bar', b: 'foo/bar').to('/foo%2Fbar/foo/bar') }
567
+ end
568
+
564
569
  context 'prefix' do
565
570
  pattern '{+a:3}/bar' do
566
571
  it { should match('foo/bar') .capturing a: 'foo' }
metadata CHANGED
@@ -1,158 +1,157 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: mustermann19
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.3.1.2
4
+ version: 0.4.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Konstantin Haase
8
8
  - namusyaka
9
- autorequire:
9
+ autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2014-11-24 00:00:00.000000000 Z
12
+ date: 2014-11-29 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
- name: enumerable-lazy
16
15
  requirement: !ruby/object:Gem::Requirement
17
16
  requirements:
18
17
  - - '>='
19
18
  - !ruby/object:Gem::Version
20
19
  version: '0'
21
- type: :runtime
20
+ name: enumerable-lazy
22
21
  prerelease: false
22
+ type: :runtime
23
23
  version_requirements: !ruby/object:Gem::Requirement
24
24
  requirements:
25
25
  - - '>='
26
26
  - !ruby/object:Gem::Version
27
27
  version: '0'
28
28
  - !ruby/object:Gem::Dependency
29
- name: rspec
30
29
  requirement: !ruby/object:Gem::Requirement
31
30
  requirements:
32
31
  - - '>='
33
32
  - !ruby/object:Gem::Version
34
33
  version: '0'
35
- type: :development
34
+ name: rspec
36
35
  prerelease: false
36
+ type: :development
37
37
  version_requirements: !ruby/object:Gem::Requirement
38
38
  requirements:
39
39
  - - '>='
40
40
  - !ruby/object:Gem::Version
41
41
  version: '0'
42
42
  - !ruby/object:Gem::Dependency
43
- name: rspec-its
44
43
  requirement: !ruby/object:Gem::Requirement
45
44
  requirements:
46
45
  - - '>='
47
46
  - !ruby/object:Gem::Version
48
47
  version: '0'
49
- type: :development
48
+ name: rspec-its
50
49
  prerelease: false
50
+ type: :development
51
51
  version_requirements: !ruby/object:Gem::Requirement
52
52
  requirements:
53
53
  - - '>='
54
54
  - !ruby/object:Gem::Version
55
55
  version: '0'
56
56
  - !ruby/object:Gem::Dependency
57
- name: addressable
58
57
  requirement: !ruby/object:Gem::Requirement
59
58
  requirements:
60
59
  - - '>='
61
60
  - !ruby/object:Gem::Version
62
61
  version: '0'
63
- type: :development
62
+ name: addressable
64
63
  prerelease: false
64
+ type: :development
65
65
  version_requirements: !ruby/object:Gem::Requirement
66
66
  requirements:
67
67
  - - '>='
68
68
  - !ruby/object:Gem::Version
69
69
  version: '0'
70
70
  - !ruby/object:Gem::Dependency
71
- name: sinatra
72
71
  requirement: !ruby/object:Gem::Requirement
73
72
  requirements:
74
73
  - - ~>
75
74
  - !ruby/object:Gem::Version
76
75
  version: '1.4'
77
- type: :development
76
+ name: sinatra
78
77
  prerelease: false
78
+ type: :development
79
79
  version_requirements: !ruby/object:Gem::Requirement
80
80
  requirements:
81
81
  - - ~>
82
82
  - !ruby/object:Gem::Version
83
83
  version: '1.4'
84
84
  - !ruby/object:Gem::Dependency
85
- name: rack-test
86
85
  requirement: !ruby/object:Gem::Requirement
87
86
  requirements:
88
87
  - - '>='
89
88
  - !ruby/object:Gem::Version
90
89
  version: '0'
91
- type: :development
90
+ name: rack-test
92
91
  prerelease: false
92
+ type: :development
93
93
  version_requirements: !ruby/object:Gem::Requirement
94
94
  requirements:
95
95
  - - '>='
96
96
  - !ruby/object:Gem::Version
97
97
  version: '0'
98
98
  - !ruby/object:Gem::Dependency
99
- name: rake
100
99
  requirement: !ruby/object:Gem::Requirement
101
100
  requirements:
102
101
  - - '>='
103
102
  - !ruby/object:Gem::Version
104
103
  version: '0'
105
- type: :development
104
+ name: rake
106
105
  prerelease: false
106
+ type: :development
107
107
  version_requirements: !ruby/object:Gem::Requirement
108
108
  requirements:
109
109
  - - '>='
110
110
  - !ruby/object:Gem::Version
111
111
  version: '0'
112
112
  - !ruby/object:Gem::Dependency
113
- name: yard
114
113
  requirement: !ruby/object:Gem::Requirement
115
114
  requirements:
116
115
  - - '>='
117
116
  - !ruby/object:Gem::Version
118
117
  version: '0'
119
- type: :development
118
+ name: yard
120
119
  prerelease: false
120
+ type: :development
121
121
  version_requirements: !ruby/object:Gem::Requirement
122
122
  requirements:
123
123
  - - '>='
124
124
  - !ruby/object:Gem::Version
125
125
  version: '0'
126
126
  - !ruby/object:Gem::Dependency
127
- name: simplecov
128
127
  requirement: !ruby/object:Gem::Requirement
129
128
  requirements:
130
129
  - - '>='
131
130
  - !ruby/object:Gem::Version
132
131
  version: '0'
133
- type: :development
132
+ name: simplecov
134
133
  prerelease: false
134
+ type: :development
135
135
  version_requirements: !ruby/object:Gem::Requirement
136
136
  requirements:
137
137
  - - '>='
138
138
  - !ruby/object:Gem::Version
139
139
  version: '0'
140
140
  - !ruby/object:Gem::Dependency
141
- name: coveralls
142
141
  requirement: !ruby/object:Gem::Requirement
143
142
  requirements:
144
143
  - - '>='
145
144
  - !ruby/object:Gem::Version
146
145
  version: '0'
147
- type: :development
146
+ name: coveralls
148
147
  prerelease: false
148
+ type: :development
149
149
  version_requirements: !ruby/object:Gem::Requirement
150
150
  requirements:
151
151
  - - '>='
152
152
  - !ruby/object:Gem::Version
153
153
  version: '0'
154
- description: library implementing patterns that behave like regular expressions for
155
- use in Ruby 1.9
154
+ description: library implementing patterns that behave like regular expressions for use in Ruby 1.9
156
155
  email: namusyaka@gmail.com
157
156
  executables: []
158
157
  extensions: []
@@ -172,6 +171,7 @@ files:
172
171
  - bench/simple_vs_sinatra.rb
173
172
  - bench/template_vs_addressable.rb
174
173
  - lib/mustermann.rb
174
+ - lib/mustermann/ast/boundaries.rb
175
175
  - lib/mustermann/ast/compiler.rb
176
176
  - lib/mustermann/ast/expander.rb
177
177
  - lib/mustermann/ast/node.rb
@@ -197,6 +197,8 @@ files:
197
197
  - lib/mustermann/pattern_cache.rb
198
198
  - lib/mustermann/pyramid.rb
199
199
  - lib/mustermann/rails.rb
200
+ - lib/mustermann/rails/versions.rb
201
+ - lib/mustermann/regexp.rb
200
202
  - lib/mustermann/regexp_based.rb
201
203
  - lib/mustermann/regular.rb
202
204
  - lib/mustermann/router.rb
@@ -209,8 +211,12 @@ files:
209
211
  - lib/mustermann/string_scanner.rb
210
212
  - lib/mustermann/template.rb
211
213
  - lib/mustermann/to_pattern.rb
214
+ - lib/mustermann/uri_template.rb
212
215
  - lib/mustermann/version.rb
213
216
  - mustermann.gemspec
217
+ - mustermann/README.md
218
+ - mustermann/lib/mustermann/ast/compiler.rb
219
+ - spec/ast_spec.rb
214
220
  - spec/composite_spec.rb
215
221
  - spec/expander_spec.rb
216
222
  - spec/express_spec.rb
@@ -246,7 +252,7 @@ homepage: https://github.com/namusyaka/mustermann
246
252
  licenses:
247
253
  - MIT
248
254
  metadata: {}
249
- post_install_message:
255
+ post_install_message:
250
256
  rdoc_options: []
251
257
  require_paths:
252
258
  - lib
@@ -261,12 +267,13 @@ required_rubygems_version: !ruby/object:Gem::Requirement
261
267
  - !ruby/object:Gem::Version
262
268
  version: '0'
263
269
  requirements: []
264
- rubyforge_project:
265
- rubygems_version: 2.0.14
266
- signing_key:
270
+ rubyforge_project:
271
+ rubygems_version: 2.1.9
272
+ signing_key:
267
273
  specification_version: 4
268
274
  summary: use patterns like regular expressions
269
275
  test_files:
276
+ - spec/ast_spec.rb
270
277
  - spec/composite_spec.rb
271
278
  - spec/expander_spec.rb
272
279
  - spec/express_spec.rb
@@ -298,4 +305,4 @@ test_files:
298
305
  - spec/support/scan_matcher.rb
299
306
  - spec/template_spec.rb
300
307
  - spec/to_pattern_spec.rb
301
- has_rdoc:
308
+ has_rdoc: