mustermann19 0.3.1.2 → 0.4.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.
@@ -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: