mustermann 0.0.1 → 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +2 -1
- data/.travis.yml +4 -3
- data/.yardopts +1 -0
- data/README.md +53 -10
- data/Rakefile +4 -1
- data/bench/capturing.rb +42 -9
- data/bench/template_vs_addressable.rb +3 -0
- data/internals.md +64 -0
- data/lib/mustermann.rb +14 -5
- data/lib/mustermann/ast/compiler.rb +150 -0
- data/lib/mustermann/ast/expander.rb +112 -0
- data/lib/mustermann/ast/node.rb +155 -0
- data/lib/mustermann/ast/parser.rb +136 -0
- data/lib/mustermann/ast/pattern.rb +89 -0
- data/lib/mustermann/ast/transformer.rb +121 -0
- data/lib/mustermann/ast/translator.rb +111 -0
- data/lib/mustermann/ast/tree_renderer.rb +29 -0
- data/lib/mustermann/ast/validation.rb +40 -0
- data/lib/mustermann/error.rb +4 -12
- data/lib/mustermann/extension.rb +3 -6
- data/lib/mustermann/identity.rb +4 -4
- data/lib/mustermann/pattern.rb +34 -5
- data/lib/mustermann/rails.rb +7 -16
- data/lib/mustermann/regexp_based.rb +4 -4
- data/lib/mustermann/shell.rb +4 -4
- data/lib/mustermann/simple.rb +1 -1
- data/lib/mustermann/simple_match.rb +2 -2
- data/lib/mustermann/sinatra.rb +10 -20
- data/lib/mustermann/template.rb +11 -104
- data/lib/mustermann/version.rb +1 -1
- data/mustermann.gemspec +1 -1
- data/spec/extension_spec.rb +143 -0
- data/spec/mustermann_spec.rb +41 -0
- data/spec/pattern_spec.rb +16 -6
- data/spec/rails_spec.rb +77 -9
- data/spec/sinatra_spec.rb +6 -0
- data/spec/support.rb +5 -78
- data/spec/support/coverage.rb +18 -0
- data/spec/support/env.rb +6 -0
- data/spec/support/expand_matcher.rb +27 -0
- data/spec/support/match_matcher.rb +39 -0
- data/spec/support/pattern.rb +28 -0
- metadata +43 -43
- data/.test_queue_stats +0 -0
- data/lib/mustermann/ast.rb +0 -403
- data/spec/ast_spec.rb +0 -8
@@ -3,16 +3,16 @@ require 'forwardable'
|
|
3
3
|
|
4
4
|
module Mustermann
|
5
5
|
# Superclass for patterns that internally compile to a regular expression.
|
6
|
-
# @see Pattern
|
6
|
+
# @see Mustermann::Pattern
|
7
7
|
# @abstract
|
8
8
|
class RegexpBased < Pattern
|
9
9
|
# @return [Regexp] regular expression equivalent to the pattern.
|
10
10
|
attr_reader :regexp
|
11
11
|
alias_method :to_regexp, :regexp
|
12
12
|
|
13
|
-
# @param (see Pattern#initialize)
|
14
|
-
# @return (see Pattern#initialize)
|
15
|
-
# @see (see Pattern#initialize)
|
13
|
+
# @param (see Mustermann::Pattern#initialize)
|
14
|
+
# @return (see Mustermann::Pattern#initialize)
|
15
|
+
# @see (see Mustermann::Pattern#initialize)
|
16
16
|
def initialize(string, **options)
|
17
17
|
@regexp = compile(string, **options)
|
18
18
|
super
|
data/lib/mustermann/shell.rb
CHANGED
@@ -7,14 +7,14 @@ module Mustermann
|
|
7
7
|
# @example
|
8
8
|
# Mustermann.new('/*.*', type: :shell) === '/bar' # => false
|
9
9
|
#
|
10
|
-
# @see Pattern
|
10
|
+
# @see Mustermann::Pattern
|
11
11
|
# @see file:README.md#shell Syntax description in the README
|
12
12
|
class Shell < Pattern
|
13
13
|
FLAGS ||= File::FNM_PATHNAME | File::FNM_DOTMATCH | File::FNM_EXTGLOB
|
14
14
|
|
15
|
-
# @param (see Pattern#===)
|
16
|
-
# @return (see Pattern#===)
|
17
|
-
# @see (see Pattern#===)
|
15
|
+
# @param (see Mustermann::Pattern#===)
|
16
|
+
# @return (see Mustermann::Pattern#===)
|
17
|
+
# @see (see Mustermann::Pattern#===)
|
18
18
|
def ===(string)
|
19
19
|
File.fnmatch? @string, unescape(string), FLAGS
|
20
20
|
end
|
data/lib/mustermann/simple.rb
CHANGED
@@ -6,7 +6,7 @@ module Mustermann
|
|
6
6
|
# @example
|
7
7
|
# Mustermann.new('/:foo', type: :simple) === '/bar' # => true
|
8
8
|
#
|
9
|
-
# @see Pattern
|
9
|
+
# @see Mustermann::Pattern
|
10
10
|
# @see file:README.md#simple Syntax description in the README
|
11
11
|
class Simple < RegexpBased
|
12
12
|
supported_options :greedy, :space_matches_plus
|
@@ -12,12 +12,12 @@ module Mustermann
|
|
12
12
|
@string.dup
|
13
13
|
end
|
14
14
|
|
15
|
-
# @return [Array<String>] empty array for
|
15
|
+
# @return [Array<String>] empty array for imitating MatchData interface
|
16
16
|
def names
|
17
17
|
[]
|
18
18
|
end
|
19
19
|
|
20
|
-
# @return [Array<String>] empty array for
|
20
|
+
# @return [Array<String>] empty array for imitating MatchData interface
|
21
21
|
def captures
|
22
22
|
[]
|
23
23
|
end
|
data/lib/mustermann/sinatra.rb
CHANGED
@@ -1,4 +1,4 @@
|
|
1
|
-
require 'mustermann/ast'
|
1
|
+
require 'mustermann/ast/pattern'
|
2
2
|
|
3
3
|
module Mustermann
|
4
4
|
# Sinatra 2.0 style pattern implementation.
|
@@ -6,27 +6,17 @@ module Mustermann
|
|
6
6
|
# @example
|
7
7
|
# Mustermann.new('/:foo') === '/bar' # => true
|
8
8
|
#
|
9
|
-
# @see Pattern
|
9
|
+
# @see Mustermann::Pattern
|
10
10
|
# @see file:README.md#sinatra Syntax description in the README
|
11
|
-
class Sinatra < AST
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
when ?/ then Separator.new(char)
|
18
|
-
when ?( then Group.parse { parse_buffer(buffer) }
|
19
|
-
when ?: then Capture.parse { buffer.scan(/\w+/) }
|
20
|
-
when ?\\ then Char.new expect(buffer, /./)
|
21
|
-
else Char.new(char)
|
22
|
-
end
|
23
|
-
end
|
11
|
+
class Sinatra < AST::Pattern
|
12
|
+
on(nil, ??, ?)) { |c| unexpected(c) }
|
13
|
+
on(?*) { |c| node(:splat) }
|
14
|
+
on(?() { |c| node(:group) { read unless scan(?)) } }
|
15
|
+
on(?:) { |c| node(:capture) { scan(/\w+/) } }
|
16
|
+
on(?\\) { |c| node(:char, expect(/./)) }
|
24
17
|
|
25
|
-
|
26
|
-
|
27
|
-
Optional.new(element)
|
18
|
+
suffix ?? do |char, element|
|
19
|
+
node(:optional, element)
|
28
20
|
end
|
29
|
-
|
30
|
-
private :parse_element, :parse_suffix
|
31
21
|
end
|
32
22
|
end
|
data/lib/mustermann/template.rb
CHANGED
@@ -1,4 +1,4 @@
|
|
1
|
-
require 'mustermann/ast'
|
1
|
+
require 'mustermann/ast/pattern'
|
2
2
|
|
3
3
|
module Mustermann
|
4
4
|
# URI template pattern implementation.
|
@@ -6,115 +6,22 @@ module Mustermann
|
|
6
6
|
# @example
|
7
7
|
# Mustermann.new('/{foo}') === '/bar' # => true
|
8
8
|
#
|
9
|
-
# @see Pattern
|
9
|
+
# @see Mustermann::Pattern
|
10
10
|
# @see file:README.md#template Syntax description in the README
|
11
11
|
# @see http://tools.ietf.org/html/rfc6570 RFC 6570
|
12
|
-
class Template < AST
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
?/ => Operator.new(?/, false, ?/, false), ?; => Operator.new(?;, false, ?;, true),
|
18
|
-
?? => Operator.new(?&, false, ??, true), ?& => Operator.new(?&, false, ?&, true)
|
19
|
-
}
|
20
|
-
|
21
|
-
# AST node for template expressions.
|
22
|
-
# @!visibility private
|
23
|
-
class Expression < Group
|
24
|
-
# @!visibility private
|
25
|
-
attr_accessor :operator
|
26
|
-
|
27
|
-
# makes sure we have the proper surrounding characters for the operator
|
28
|
-
# @!visibility private
|
29
|
-
def transform
|
30
|
-
self.operator = OPERATORS.fetch(operator) { raise CompileError, "#{operator} operator not supported" }
|
31
|
-
new_payload = payload.inject { |list, element| Array(list) << separator << element }
|
32
|
-
@payload = Array(new_payload).map!(&:transform)
|
33
|
-
payload.unshift separator(operator.prefix) if operator.prefix
|
34
|
-
self
|
35
|
-
end
|
36
|
-
|
37
|
-
# @!visibility private
|
38
|
-
def compile(greedy: true, **options)
|
39
|
-
super(allow_reserved: operator.allow_reserved, greedy: greedy && !operator.allow_reserved,
|
40
|
-
parametric: operator.parametric, separator: operator.separator, **options)
|
41
|
-
end
|
42
|
-
|
43
|
-
# @!visibility private
|
44
|
-
def separator(char = operator.separator)
|
45
|
-
AST.const_get(:Separator).new(char) # uhm
|
46
|
-
end
|
47
|
-
end
|
48
|
-
|
49
|
-
# AST node for template variables.
|
50
|
-
# @!visibility private
|
51
|
-
class Variable < Capture
|
52
|
-
# @!visibility private
|
53
|
-
attr_accessor :prefix, :explode
|
54
|
-
|
55
|
-
# @!visibility private
|
56
|
-
def compile(**options)
|
57
|
-
return super(**options) if explode or not options[:parametric]
|
58
|
-
parametric super(parametric: false, **options)
|
59
|
-
end
|
60
|
-
|
61
|
-
# @!visibility private
|
62
|
-
def pattern(parametric: false, **options)
|
63
|
-
register_param(parametric: parametric, **options)
|
64
|
-
pattern = super(**options)
|
65
|
-
pattern = parametric(pattern) if parametric
|
66
|
-
pattern = "#{pattern}(?:#{Regexp.escape(options.fetch(:separator))}#{pattern})*" if explode
|
67
|
-
pattern
|
68
|
-
end
|
69
|
-
|
70
|
-
# @!visibility private
|
71
|
-
def parametric(string)
|
72
|
-
"#{Regexp.escape(name)}(?:=#{string})?"
|
12
|
+
class Template < AST::Pattern
|
13
|
+
on ?{ do |char|
|
14
|
+
variable = proc do
|
15
|
+
match = expect(/(?<name>\w+)(?:\:(?<prefix>\d{1,4})|(?<explode>\*))?/)
|
16
|
+
node(:variable, match[:name], prefix: match[:prefix], explode: match[:explode])
|
73
17
|
end
|
74
18
|
|
75
|
-
# @!visibility private
|
76
|
-
def qualified(string, **options)
|
77
|
-
prefix ? "#{string}{1,#{prefix}}" : super(string, **options)
|
78
|
-
end
|
79
|
-
|
80
|
-
# @!visibility private
|
81
|
-
def default(allow_reserved: false, **options)
|
82
|
-
allow_reserved ? '[\w\-\.~%\:/\?#\[\]@\!\$\&\'\(\)\*\+,;=]' : '[\w\-\.~%]'
|
83
|
-
end
|
84
|
-
|
85
|
-
# @!visibility private
|
86
|
-
def register_param(parametric: false, split_params: nil, separator: nil, **options)
|
87
|
-
return unless explode and split_params
|
88
|
-
split_params[name] = { separator: separator, parametric: parametric }
|
89
|
-
end
|
90
|
-
end
|
91
|
-
|
92
|
-
# @!visibility private
|
93
|
-
def parse_element(buffer)
|
94
|
-
parse_expression(buffer) || parse_literal(buffer)
|
95
|
-
end
|
96
|
-
|
97
|
-
# @!visibility private
|
98
|
-
def parse_expression(buffer)
|
99
|
-
return unless buffer.scan(/\{/)
|
100
19
|
operator = buffer.scan(/[\+\#\.\/;\?\&\=\,\!\@\|]/)
|
101
|
-
expression =
|
102
|
-
expression
|
103
|
-
expression if expect(buffer, ?})
|
104
|
-
end
|
105
|
-
|
106
|
-
# @!visibility private
|
107
|
-
def parse_variable(buffer)
|
108
|
-
match = expect(buffer, /(?<name>\w+)(?:\:(?<prefix>\d{1,4})|(?<explode>\*))?/)
|
109
|
-
Variable.new(match[:name], prefix: match[:prefix], explode: match[:explode])
|
20
|
+
expression = node(:expression, [variable[]], operator: operator) { variable[] if scan(?,) }
|
21
|
+
expression if expect(?})
|
110
22
|
end
|
111
23
|
|
112
|
-
|
113
|
-
def parse_literal(buffer)
|
114
|
-
return unless char = buffer.getch
|
115
|
-
raise unexpected(?}) if char == ?}
|
116
|
-
char == ?/ ? Separator.new('/') : Char.new(char)
|
117
|
-
end
|
24
|
+
on(?}) { |c| unexpected(c) }
|
118
25
|
|
119
26
|
# @!visibility private
|
120
27
|
def compile(*args, **options)
|
@@ -135,6 +42,6 @@ module Mustermann
|
|
135
42
|
@split_params.include? key
|
136
43
|
end
|
137
44
|
|
138
|
-
private :
|
45
|
+
private :compile, :map_param, :always_array?
|
139
46
|
end
|
140
47
|
end
|
data/lib/mustermann/version.rb
CHANGED
data/mustermann.gemspec
CHANGED
@@ -13,7 +13,7 @@ Gem::Specification.new do |s|
|
|
13
13
|
s.files = `git ls-files`.split("\n")
|
14
14
|
s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
|
15
15
|
s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
|
16
|
-
s.extra_rdoc_files = %w[README.md]
|
16
|
+
s.extra_rdoc_files = %w[README.md internals.md]
|
17
17
|
s.require_path = 'lib'
|
18
18
|
s.required_ruby_version = '>= 2.0.0'
|
19
19
|
|
data/spec/extension_spec.rb
CHANGED
@@ -44,6 +44,7 @@ describe Mustermann::Extension do
|
|
44
44
|
|
45
45
|
context 'route local' do
|
46
46
|
before do
|
47
|
+
app.set(:pattern, nil)
|
47
48
|
app.get('/:id', capture: /\d+/) { 'ok' }
|
48
49
|
end
|
49
50
|
|
@@ -84,6 +85,148 @@ describe Mustermann::Extension do
|
|
84
85
|
end
|
85
86
|
end
|
86
87
|
|
88
|
+
describe :pattern do
|
89
|
+
describe :except do
|
90
|
+
before { app.get('/auth/*', pattern: { except: '/auth/login' }) { 'ok' } }
|
91
|
+
example { get('/auth/dunno').should be_ok }
|
92
|
+
example { get('/auth/login').should_not be_ok }
|
93
|
+
end
|
94
|
+
|
95
|
+
describe :capture do
|
96
|
+
context 'route local' do
|
97
|
+
before do
|
98
|
+
app.set(:pattern, nil)
|
99
|
+
app.get('/:id', pattern: { capture: /\d+/ }) { 'ok' }
|
100
|
+
end
|
101
|
+
|
102
|
+
example { get('/42').should be_ok }
|
103
|
+
example { get('/foo').should_not be_ok }
|
104
|
+
end
|
105
|
+
|
106
|
+
context 'global and route local' do
|
107
|
+
context 'global is a hash' do
|
108
|
+
before do
|
109
|
+
app.set(:pattern, capture: { id: /\d+/ })
|
110
|
+
app.get('/:id(.:ext)?', pattern: { capture: { ext: 'png' }}) { ?a }
|
111
|
+
app.get('/:id', pattern: { capture: { id: 'foo' }}) { ?b }
|
112
|
+
app.get('/:id', pattern: { capture: :alpha }) { ?c }
|
113
|
+
end
|
114
|
+
|
115
|
+
example { get('/20') .body.should be == ?a }
|
116
|
+
example { get('/20.png') .body.should be == ?a }
|
117
|
+
example { get('/foo') .body.should be == ?b }
|
118
|
+
example { get('/bar') .body.should be == ?c }
|
119
|
+
end
|
120
|
+
|
121
|
+
context 'global is not a hash' do
|
122
|
+
before do
|
123
|
+
app.set(:pattern, capture: /\d+/)
|
124
|
+
app.get('/:slug(.:ext)?', pattern: { capture: { ext: 'png' }}) { params[:slug] }
|
125
|
+
app.get('/:slug', pattern: { capture: :alpha }) { 'ok' }
|
126
|
+
end
|
127
|
+
|
128
|
+
example { get('/20.png').should be_ok }
|
129
|
+
example { get('/foo.png').should_not be_ok }
|
130
|
+
example { get('/foo').should be_ok }
|
131
|
+
|
132
|
+
example { get('/20.png') .body.should be == '20' }
|
133
|
+
example { get('/42') .body.should be == '42' }
|
134
|
+
example { get('/foo') .body.should be == 'ok' }
|
135
|
+
end
|
136
|
+
end
|
137
|
+
end
|
138
|
+
|
139
|
+
describe :greedy do
|
140
|
+
context 'default' do
|
141
|
+
before { app.get('/:name.:ext') { params[:name] }}
|
142
|
+
example { get('/foo.bar') .body.should be == 'foo' }
|
143
|
+
example { get('/foo.bar.baz') .body.should be == 'foo.bar' }
|
144
|
+
end
|
145
|
+
|
146
|
+
context 'enabled' do
|
147
|
+
before { app.get('/:name.:ext', pattern: { greedy: true }) { params[:name] }}
|
148
|
+
example { get('/foo.bar') .body.should be == 'foo' }
|
149
|
+
example { get('/foo.bar.baz') .body.should be == 'foo.bar' }
|
150
|
+
end
|
151
|
+
|
152
|
+
context 'disabled' do
|
153
|
+
before { app.get('/:name.:ext', pattern: { greedy: false }) { params[:name] }}
|
154
|
+
example { get('/foo.bar') .body.should be == 'foo' }
|
155
|
+
example { get('/foo.bar.baz') .body.should be == 'foo' }
|
156
|
+
end
|
157
|
+
|
158
|
+
context 'global' do
|
159
|
+
before do
|
160
|
+
app.set(:pattern, greedy: false)
|
161
|
+
app.get('/:name.:ext') { params[:name] }
|
162
|
+
end
|
163
|
+
|
164
|
+
example { get('/foo.bar') .body.should be == 'foo' }
|
165
|
+
example { get('/foo.bar.baz') .body.should be == 'foo' }
|
166
|
+
end
|
167
|
+
end
|
168
|
+
|
169
|
+
describe :space_matches_plus do
|
170
|
+
context 'default' do
|
171
|
+
before { app.get('/foo bar') { 'ok' }}
|
172
|
+
example { get('/foo%20bar') .should be_ok }
|
173
|
+
example { get('/foo+bar') .should be_ok }
|
174
|
+
end
|
175
|
+
|
176
|
+
context 'enabled' do
|
177
|
+
before { app.get('/foo bar', pattern: { space_matches_plus: true }) { 'ok' }}
|
178
|
+
example { get('/foo%20bar') .should be_ok }
|
179
|
+
example { get('/foo+bar') .should be_ok }
|
180
|
+
end
|
181
|
+
|
182
|
+
context 'disabled' do
|
183
|
+
before { app.get('/foo bar', pattern: { space_matches_plus: false }) { 'ok' }}
|
184
|
+
example { get('/foo%20bar') .should be_ok }
|
185
|
+
example { get('/foo+bar') .should_not be_ok }
|
186
|
+
end
|
187
|
+
|
188
|
+
context 'global' do
|
189
|
+
before do
|
190
|
+
app.set(:pattern, space_matches_plus: false)
|
191
|
+
app.get('/foo bar') { 'ok' }
|
192
|
+
end
|
193
|
+
|
194
|
+
example { get('/foo%20bar') .should be_ok }
|
195
|
+
example { get('/foo+bar') .should_not be_ok }
|
196
|
+
end
|
197
|
+
end
|
198
|
+
|
199
|
+
describe :uri_decode do
|
200
|
+
context 'default' do
|
201
|
+
before { app.get('/&') { 'ok' }}
|
202
|
+
example { get('/&') .should be_ok }
|
203
|
+
example { get('/%26') .should be_ok }
|
204
|
+
end
|
205
|
+
|
206
|
+
context 'enabled' do
|
207
|
+
before { app.get('/&', pattern: { uri_decode: true }) { 'ok' }}
|
208
|
+
example { get('/&') .should be_ok }
|
209
|
+
example { get('/%26') .should be_ok }
|
210
|
+
end
|
211
|
+
|
212
|
+
context 'disabled' do
|
213
|
+
before { app.get('/&', pattern: { uri_decode: false }) { 'ok' }}
|
214
|
+
example { get('/&') .should be_ok }
|
215
|
+
example { get('/%26') .should_not be_ok }
|
216
|
+
end
|
217
|
+
|
218
|
+
context 'global' do
|
219
|
+
before do
|
220
|
+
app.set(:pattern, uri_decode: false)
|
221
|
+
app.get('/&') { 'ok' }
|
222
|
+
end
|
223
|
+
|
224
|
+
example { get('/&') .should be_ok }
|
225
|
+
example { get('/%26') .should_not be_ok }
|
226
|
+
end
|
227
|
+
end
|
228
|
+
end
|
229
|
+
|
87
230
|
describe :type do
|
88
231
|
describe :identity do
|
89
232
|
before do
|
data/spec/mustermann_spec.rb
CHANGED
@@ -0,0 +1,41 @@
|
|
1
|
+
require 'support'
|
2
|
+
require 'mustermann'
|
3
|
+
require 'mustermann/extension'
|
4
|
+
require 'sinatra/base'
|
5
|
+
|
6
|
+
describe Mustermann do
|
7
|
+
describe :new do
|
8
|
+
example { Mustermann.new('') .should be_a(Mustermann::Sinatra) }
|
9
|
+
example { Mustermann.new('', type: :identity) .should be_a(Mustermann::Identity) }
|
10
|
+
example { Mustermann.new('', type: :rails) .should be_a(Mustermann::Rails) }
|
11
|
+
example { Mustermann.new('', type: :shell) .should be_a(Mustermann::Shell) }
|
12
|
+
example { Mustermann.new('', type: :sinatra) .should be_a(Mustermann::Sinatra) }
|
13
|
+
example { Mustermann.new('', type: :simple) .should be_a(Mustermann::Simple) }
|
14
|
+
example { Mustermann.new('', type: :template) .should be_a(Mustermann::Template) }
|
15
|
+
|
16
|
+
example { expect { Mustermann.new('', foo: :bar) }.to raise_error(ArgumentError, "unsupported option :foo for Mustermann::Sinatra") }
|
17
|
+
example { expect { Mustermann.new('', type: :ast) }.to raise_error(ArgumentError, "unsupported type :ast") }
|
18
|
+
end
|
19
|
+
|
20
|
+
describe :[] do
|
21
|
+
example { Mustermann[:identity] .should be == Mustermann::Identity }
|
22
|
+
example { Mustermann[:rails] .should be == Mustermann::Rails }
|
23
|
+
example { Mustermann[:shell] .should be == Mustermann::Shell }
|
24
|
+
example { Mustermann[:sinatra] .should be == Mustermann::Sinatra }
|
25
|
+
example { Mustermann[:simple] .should be == Mustermann::Simple }
|
26
|
+
example { Mustermann[:template] .should be == Mustermann::Template }
|
27
|
+
|
28
|
+
example { expect { Mustermann[:ast] }.to raise_error(ArgumentError, "unsupported type :ast") }
|
29
|
+
end
|
30
|
+
|
31
|
+
describe :extend_object do
|
32
|
+
context 'special behavior for Sinatra only' do
|
33
|
+
example { Object .new.extend(Mustermann).should be_a(Mustermann) }
|
34
|
+
example { Object .new.extend(Mustermann).should_not be_a(Mustermann::Extension) }
|
35
|
+
example { Class .new.extend(Mustermann).should be_a(Mustermann) }
|
36
|
+
example { Class .new.extend(Mustermann).should_not be_a(Mustermann::Extension) }
|
37
|
+
example { Sinatra .new.extend(Mustermann).should_not be_a(Mustermann) }
|
38
|
+
example { Sinatra .new.extend(Mustermann).should be_a(Mustermann::Extension) }
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|