mustermann19 0.3.1
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +18 -0
- data/.rspec +2 -0
- data/.travis.yml +10 -0
- data/.yardopts +1 -0
- data/Gemfile +2 -0
- data/LICENSE +22 -0
- data/README.md +1081 -0
- data/Rakefile +6 -0
- data/bench/capturing.rb +57 -0
- data/bench/regexp.rb +21 -0
- data/bench/simple_vs_sinatra.rb +23 -0
- data/bench/template_vs_addressable.rb +26 -0
- data/internals.md +64 -0
- data/lib/mustermann.rb +61 -0
- data/lib/mustermann/ast/compiler.rb +168 -0
- data/lib/mustermann/ast/expander.rb +134 -0
- data/lib/mustermann/ast/node.rb +160 -0
- data/lib/mustermann/ast/parser.rb +137 -0
- data/lib/mustermann/ast/pattern.rb +84 -0
- data/lib/mustermann/ast/transformer.rb +129 -0
- data/lib/mustermann/ast/translator.rb +108 -0
- data/lib/mustermann/ast/tree_renderer.rb +29 -0
- data/lib/mustermann/ast/validation.rb +43 -0
- data/lib/mustermann/caster.rb +117 -0
- data/lib/mustermann/equality_map.rb +48 -0
- data/lib/mustermann/error.rb +6 -0
- data/lib/mustermann/expander.rb +206 -0
- data/lib/mustermann/extension.rb +52 -0
- data/lib/mustermann/identity.rb +19 -0
- data/lib/mustermann/mapper.rb +98 -0
- data/lib/mustermann/pattern.rb +182 -0
- data/lib/mustermann/rails.rb +17 -0
- data/lib/mustermann/regexp_based.rb +30 -0
- data/lib/mustermann/regular.rb +26 -0
- data/lib/mustermann/router.rb +9 -0
- data/lib/mustermann/router/rack.rb +50 -0
- data/lib/mustermann/router/simple.rb +144 -0
- data/lib/mustermann/shell.rb +29 -0
- data/lib/mustermann/simple.rb +38 -0
- data/lib/mustermann/simple_match.rb +30 -0
- data/lib/mustermann/sinatra.rb +22 -0
- data/lib/mustermann/template.rb +48 -0
- data/lib/mustermann/to_pattern.rb +45 -0
- data/lib/mustermann/version.rb +3 -0
- data/mustermann.gemspec +31 -0
- data/spec/expander_spec.rb +105 -0
- data/spec/extension_spec.rb +296 -0
- data/spec/identity_spec.rb +83 -0
- data/spec/mapper_spec.rb +83 -0
- data/spec/mustermann_spec.rb +65 -0
- data/spec/pattern_spec.rb +49 -0
- data/spec/rails_spec.rb +522 -0
- data/spec/regexp_based_spec.rb +8 -0
- data/spec/regular_spec.rb +36 -0
- data/spec/router/rack_spec.rb +39 -0
- data/spec/router/simple_spec.rb +32 -0
- data/spec/shell_spec.rb +109 -0
- data/spec/simple_match_spec.rb +10 -0
- data/spec/simple_spec.rb +237 -0
- data/spec/sinatra_spec.rb +574 -0
- data/spec/support.rb +5 -0
- data/spec/support/coverage.rb +16 -0
- data/spec/support/env.rb +15 -0
- data/spec/support/expand_matcher.rb +27 -0
- data/spec/support/match_matcher.rb +39 -0
- data/spec/support/pattern.rb +39 -0
- data/spec/template_spec.rb +815 -0
- data/spec/to_pattern_spec.rb +20 -0
- metadata +301 -0
@@ -0,0 +1,52 @@
|
|
1
|
+
require 'sinatra/version'
|
2
|
+
fail "no need to load the Mustermann extension for #{::Sinatra::VERSION}" if ::Sinatra::VERSION >= '2.0.0'
|
3
|
+
|
4
|
+
require 'mustermann'
|
5
|
+
|
6
|
+
module Mustermann
|
7
|
+
# Sinatra 1.x extension switching default pattern parsing over to Mustermann.
|
8
|
+
#
|
9
|
+
# @example With classic Sinatra application
|
10
|
+
# require 'sinatra'
|
11
|
+
# require 'mustermann'
|
12
|
+
#
|
13
|
+
# register Mustermann
|
14
|
+
# get('/:id', capture: /\d+/) { ... }
|
15
|
+
#
|
16
|
+
# @example With modular Sinatra application
|
17
|
+
# require 'sinatra/base'
|
18
|
+
# require 'mustermann'
|
19
|
+
#
|
20
|
+
# class MyApp < Sinatra::Base
|
21
|
+
# register Mustermann
|
22
|
+
# get('/:id', capture: /\d+/) { ... }
|
23
|
+
# end
|
24
|
+
#
|
25
|
+
# @see file:README.md#Sinatra_Integration "Sinatra Integration" in the README
|
26
|
+
module Extension
|
27
|
+
def compile!(verb, path, block, options = {})
|
28
|
+
except = options.delete(:except)
|
29
|
+
capture = options.delete(:capture)
|
30
|
+
pattern = options.delete(:pattern) || {}
|
31
|
+
if path.respond_to? :to_str
|
32
|
+
pattern[:except] = except if except
|
33
|
+
pattern[:capture] = capture if capture
|
34
|
+
|
35
|
+
if settings.respond_to? :pattern and settings.pattern?
|
36
|
+
pattern.merge! settings.pattern do |key, local, global|
|
37
|
+
next local unless local.is_a? Hash
|
38
|
+
next global.merge(local) if global.is_a? Hash
|
39
|
+
Hash.new(global).merge! local
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
path = Mustermann.new(path, pattern)
|
44
|
+
condition { params.merge! path.params(captures: Array(params[:captures]), offset: -1) }
|
45
|
+
end
|
46
|
+
|
47
|
+
super(verb, path, block, options)
|
48
|
+
end
|
49
|
+
|
50
|
+
private :compile!
|
51
|
+
end
|
52
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
require 'mustermann/pattern'
|
2
|
+
|
3
|
+
module Mustermann
|
4
|
+
# Matches strings that are identical to the pattern.
|
5
|
+
#
|
6
|
+
# @example
|
7
|
+
# Mustermann.new('/:foo', type: :identity) === '/bar' # => false
|
8
|
+
#
|
9
|
+
# @see Mustermann::Pattern
|
10
|
+
# @see file:README.md#identity Syntax description in the README
|
11
|
+
class Identity < Pattern
|
12
|
+
# @param (see Mustermann::Pattern#===)
|
13
|
+
# @return (see Mustermann::Pattern#===)
|
14
|
+
# @see (see Mustermann::Pattern#===)
|
15
|
+
def ===(string)
|
16
|
+
unescape(string) == @string
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,98 @@
|
|
1
|
+
require 'mustermann'
|
2
|
+
require 'mustermann/expander'
|
3
|
+
|
4
|
+
module Mustermann
|
5
|
+
# A mapper allows mapping one string to another based on pattern parsing and expanding.
|
6
|
+
#
|
7
|
+
# @example
|
8
|
+
# require 'mustermann/mapper'
|
9
|
+
# mapper = Mustermann::Mapper.new("/:foo" => "/:foo.html")
|
10
|
+
# mapper['/example'] # => "/example.html"
|
11
|
+
class Mapper
|
12
|
+
# Creates a new mapper.
|
13
|
+
#
|
14
|
+
# @overload initialize(**options)
|
15
|
+
# @param options [Hash] options The options hash
|
16
|
+
# @yield block for generating mappings as a hash
|
17
|
+
# @yieldreturn [Hash] see {#update}
|
18
|
+
#
|
19
|
+
# @example
|
20
|
+
# require 'mustermann/mapper'
|
21
|
+
# Mustermann::Mapper.new(type: :rails) {{
|
22
|
+
# "/:foo" => ["/:foo.html", "/:foo.:format"]
|
23
|
+
# }}
|
24
|
+
#
|
25
|
+
# @overload initialize(**options)
|
26
|
+
# @param options [Hash] options The options hash
|
27
|
+
# @yield block for generating mappings as a hash
|
28
|
+
# @yieldparam mapper [Mustermann::Mapper] the mapper instance
|
29
|
+
#
|
30
|
+
# @example
|
31
|
+
# require 'mustermann/mapper'
|
32
|
+
# Mustermann::Mapper.new(type: :rails) do |mapper|
|
33
|
+
# mapper["/:foo"] = ["/:foo.html", "/:foo.:format"]
|
34
|
+
# end
|
35
|
+
#
|
36
|
+
# @overload initialize(map = {}, **options)
|
37
|
+
# @param map [Hash] see {#update}
|
38
|
+
# @param [Hash] options The options hash
|
39
|
+
#
|
40
|
+
# @example map before options
|
41
|
+
# require 'mustermann/mapper'
|
42
|
+
# Mustermann::Mapper.new("/:foo" => "/:foo.html", type: :rails)
|
43
|
+
#
|
44
|
+
# @example map after options
|
45
|
+
# require 'mustermann/mapper'
|
46
|
+
# Mustermann::Mapper.new(type: :rails, "/:foo" => "/:foo.html")
|
47
|
+
def initialize(options = {}, &block)
|
48
|
+
@map = []
|
49
|
+
@additional_values = options.delete(:additional_values) || :ignore
|
50
|
+
@options = options
|
51
|
+
map = @options.inject({}) do |result, entry|
|
52
|
+
result[entry[0]] = @options.delete(entry[0]) if entry[0].is_a?(String)
|
53
|
+
result
|
54
|
+
end
|
55
|
+
block.arity == 0 ? update(yield) : yield(self) if block
|
56
|
+
update(map) if map
|
57
|
+
end
|
58
|
+
|
59
|
+
# Add multiple mappings.
|
60
|
+
#
|
61
|
+
# @param map [Hash{String, Pattern: String, Pattern, Arry<String, Pattern>, Expander}] the mapping
|
62
|
+
def update(map)
|
63
|
+
map.to_hash.each_pair do |input, output|
|
64
|
+
input = Mustermann.new(input, @options.dup)
|
65
|
+
output = Expander.new(*output, @options.merge(additional_values: @additional_values)) unless output.is_a? Expander
|
66
|
+
@map << [input, output]
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
# @return [Hash{Patttern: Expander}] Hash version of the mapper.
|
71
|
+
def to_h
|
72
|
+
Hash[@map]
|
73
|
+
end
|
74
|
+
|
75
|
+
# Convert a string according to mappings. You can pass in additional params.
|
76
|
+
#
|
77
|
+
# @example mapping with and without additional parameters
|
78
|
+
# mapper = Mustermann::Mapper.new("/:example" => "(/:prefix)?/:example.html")
|
79
|
+
#
|
80
|
+
def convert(input, values = {})
|
81
|
+
@map.inject(input) do |current, (pattern, expander)|
|
82
|
+
params = pattern.params(current)
|
83
|
+
params &&= Hash[values.merge(params).map { |k,v| [k.to_s, v] }]
|
84
|
+
expander.expandable?(params) ? expander.expand(params) : current
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
# Add a single mapping.
|
89
|
+
#
|
90
|
+
# @param key [String, Pattern] format of the input string
|
91
|
+
# @param value [String, Pattern, Arry<String, Pattern>, Expander] format of the output string
|
92
|
+
def []=(key, value)
|
93
|
+
update key => value
|
94
|
+
end
|
95
|
+
|
96
|
+
alias_method :[], :convert
|
97
|
+
end
|
98
|
+
end
|
@@ -0,0 +1,182 @@
|
|
1
|
+
require 'mustermann/error'
|
2
|
+
require 'mustermann/simple_match'
|
3
|
+
require 'mustermann/equality_map'
|
4
|
+
require 'uri'
|
5
|
+
|
6
|
+
module Mustermann
|
7
|
+
# Superclass for all pattern implementations.
|
8
|
+
# @abstract
|
9
|
+
class Pattern
|
10
|
+
include Mustermann
|
11
|
+
|
12
|
+
# List of supported options.
|
13
|
+
#
|
14
|
+
# @overload supported_options
|
15
|
+
# @return [Array<Symbol>] list of supported options
|
16
|
+
# @overload supported_options(*list)
|
17
|
+
# Adds options to the list.
|
18
|
+
#
|
19
|
+
# @api private
|
20
|
+
# @param [Symbol] *list adds options to the list of supported options
|
21
|
+
# @return [Array<Symbol>] list of supported options
|
22
|
+
def self.supported_options(*list)
|
23
|
+
@supported_options ||= []
|
24
|
+
options = @supported_options.concat(list)
|
25
|
+
options += superclass.supported_options if self < Pattern
|
26
|
+
options
|
27
|
+
end
|
28
|
+
|
29
|
+
# @param [Symbol] option The option to check.
|
30
|
+
# @return [Boolean] Whether or not option is supported.
|
31
|
+
def self.supported?(option)
|
32
|
+
supported_options.include? option
|
33
|
+
end
|
34
|
+
|
35
|
+
# @overload new(string, **options)
|
36
|
+
# @param (see #initialize)
|
37
|
+
# @raise (see #initialize)
|
38
|
+
# @raise [ArgumentError] if some option is not supported
|
39
|
+
# @return [Mustermann::Pattern] a new instance of Mustermann::Pattern
|
40
|
+
# @see #initialize
|
41
|
+
def self.new(string, options = {})
|
42
|
+
ignore_unknown_options = options.fetch(:ignore_unknown_options, false)
|
43
|
+
options.delete(:ignore_unknown_options)
|
44
|
+
unless ignore_unknown_options
|
45
|
+
unsupported = options.keys.detect { |key| not supported?(key) }
|
46
|
+
raise ArgumentError, "unsupported option %p for %p" % [unsupported, self] if unsupported
|
47
|
+
end
|
48
|
+
|
49
|
+
@map ||= EqualityMap.new
|
50
|
+
@map.fetch(string, options) { super(string, options) }
|
51
|
+
end
|
52
|
+
|
53
|
+
supported_options :uri_decode, :ignore_unknown_options
|
54
|
+
|
55
|
+
# @overload initialize(string, **options)
|
56
|
+
# @param [String] string the string representation of the pattern
|
57
|
+
# @param [Hash] options options for fine-tuning the pattern behavior
|
58
|
+
# @raise [Mustermann::Error] if the pattern can't be generated from the string
|
59
|
+
# @see file:README.md#Types_and_Options "Types and Options" in the README
|
60
|
+
# @see Mustermann.new
|
61
|
+
def initialize(string, options = {})
|
62
|
+
uri_decode = options.fetch(:uri_decode, true)
|
63
|
+
@uri_decode = uri_decode
|
64
|
+
@string = string.to_s.dup
|
65
|
+
end
|
66
|
+
|
67
|
+
# @return [String] the string representation of the pattern
|
68
|
+
def to_s
|
69
|
+
@string.dup
|
70
|
+
end
|
71
|
+
|
72
|
+
# @param [String] string The string to match against
|
73
|
+
# @return [MatchData, Mustermann::SimpleMatch, nil] MatchData or similar object if the pattern matches.
|
74
|
+
# @see http://ruby-doc.org/core-2.0/Regexp.html#method-i-match Regexp#match
|
75
|
+
# @see http://ruby-doc.org/core-2.0/MatchData.html MatchData
|
76
|
+
# @see Mustermann::SimpleMatch
|
77
|
+
def match(string)
|
78
|
+
SimpleMatch.new(string) if self === string
|
79
|
+
end
|
80
|
+
|
81
|
+
# @param [String] string The string to match against
|
82
|
+
# @return [Integer, nil] nil if pattern does not match the string, zero if it does.
|
83
|
+
# @see http://ruby-doc.org/core-2.0/Regexp.html#method-i-3D-7E Regexp#=~
|
84
|
+
def =~(string)
|
85
|
+
0 if self === string
|
86
|
+
end
|
87
|
+
|
88
|
+
# @param [String] string The string to match against
|
89
|
+
# @return [Boolean] Whether or not the pattern matches the given string
|
90
|
+
# @note Needs to be overridden by subclass.
|
91
|
+
# @see http://ruby-doc.org/core-2.0/Regexp.html#method-i-3D-3D-3D Regexp#===
|
92
|
+
def ===(string)
|
93
|
+
raise NotImplementedError, 'subclass responsibility'
|
94
|
+
end
|
95
|
+
|
96
|
+
# @return [Array<String>] capture names.
|
97
|
+
# @see http://ruby-doc.org/core-2.0/Regexp.html#method-i-named_captures Regexp#named_captures
|
98
|
+
def named_captures
|
99
|
+
{}
|
100
|
+
end
|
101
|
+
|
102
|
+
# @return [Hash{String: Array<Integer>}] capture names mapped to capture index.
|
103
|
+
# @see http://ruby-doc.org/core-2.0/Regexp.html#method-i-names Regexp#names
|
104
|
+
def names
|
105
|
+
[]
|
106
|
+
end
|
107
|
+
|
108
|
+
# @param [String] string the string to match against
|
109
|
+
# @return [Hash{String: String, Array<String>}, nil] Sinatra style params if pattern matches.
|
110
|
+
def params(string = nil, options = {})
|
111
|
+
options, string = string, nil if string.is_a?(Hash)
|
112
|
+
captures = options[:captures]
|
113
|
+
offset = options[:offset] || 0
|
114
|
+
return unless captures ||= match(string)
|
115
|
+
params = named_captures.map do |name, positions|
|
116
|
+
values = positions.map { |pos| map_param(name, captures[pos + offset]) }.flatten
|
117
|
+
values = values.first if values.size < 2 and not always_array? name
|
118
|
+
[name, values]
|
119
|
+
end
|
120
|
+
|
121
|
+
Hash[params]
|
122
|
+
end
|
123
|
+
|
124
|
+
# @note This method is only implemented by certain subclasses.
|
125
|
+
#
|
126
|
+
# @example Expanding a pattern
|
127
|
+
# pattern = Mustermann.new('/:name(.:ext)?')
|
128
|
+
# pattern.expand(name: 'hello') # => "/hello"
|
129
|
+
# pattern.expand(name: 'hello', ext: 'png') # => "/hello.png"
|
130
|
+
#
|
131
|
+
# @example Checking if a pattern supports expanding
|
132
|
+
# if pattern.respond_to? :expand
|
133
|
+
# pattern.expand(name: "foo")
|
134
|
+
# else
|
135
|
+
# warn "does not support expanding"
|
136
|
+
# end
|
137
|
+
#
|
138
|
+
# @param [Hash{Symbol: #to_s, Array<#to_s>}] values values to use for expansion
|
139
|
+
# @return [String] expanded string
|
140
|
+
# @raise [NotImplementedError] raised if expand is not supported.
|
141
|
+
# @raise [Mustermann::ExpandError] raised if a value is missing or unknown
|
142
|
+
# @see Mustermann::Expander
|
143
|
+
def expand(values = {})
|
144
|
+
raise NotImplementedError, "expanding not supported by #{self.class}"
|
145
|
+
end
|
146
|
+
|
147
|
+
# @!visibility private
|
148
|
+
# @return [Boolean]
|
149
|
+
# @see Object#respond_to?
|
150
|
+
def respond_to?(method, *args)
|
151
|
+
method.to_s == 'expand' ? false : super
|
152
|
+
end
|
153
|
+
|
154
|
+
# @!visibility private
|
155
|
+
def inspect
|
156
|
+
"#<%p:%p>" % [self.class, @string]
|
157
|
+
end
|
158
|
+
|
159
|
+
# @!visibility private
|
160
|
+
def map_param(key, value)
|
161
|
+
unescape(value, true)
|
162
|
+
end
|
163
|
+
|
164
|
+
# @!visibility private
|
165
|
+
def unescape(string, decode = @uri_decode)
|
166
|
+
return string unless decode and string
|
167
|
+
@uri ||= URI::Parser.new
|
168
|
+
@uri.unescape(string)
|
169
|
+
end
|
170
|
+
|
171
|
+
# @!visibility private
|
172
|
+
ALWAYS_ARRAY = %w[splat captures]
|
173
|
+
|
174
|
+
# @!visibility private
|
175
|
+
def always_array?(key)
|
176
|
+
ALWAYS_ARRAY.include? key
|
177
|
+
end
|
178
|
+
|
179
|
+
private :unescape, :map_param
|
180
|
+
#private_constant :ALWAYS_ARRAY
|
181
|
+
end
|
182
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
require 'mustermann/ast/pattern'
|
2
|
+
|
3
|
+
module Mustermann
|
4
|
+
# Rails style pattern implementation.
|
5
|
+
#
|
6
|
+
# @example
|
7
|
+
# Mustermann.new('/:foo', type: :rails) === '/bar' # => true
|
8
|
+
#
|
9
|
+
# @see Mustermann::Pattern
|
10
|
+
# @see file:README.md#rails Syntax description in the README
|
11
|
+
class Rails < AST::Pattern
|
12
|
+
on(nil, ?)) { |c| unexpected(c) }
|
13
|
+
on(?*) { |c| node(:named_splat) { scan(/\w+/) } }
|
14
|
+
on(?() { |c| node(:optional, node(:group) { read unless scan(?)) }) }
|
15
|
+
on(?:) { |c| node(:capture) { scan(/\w+/) } }
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
require 'mustermann/pattern'
|
2
|
+
require 'forwardable'
|
3
|
+
|
4
|
+
module Mustermann
|
5
|
+
# Superclass for patterns that internally compile to a regular expression.
|
6
|
+
# @see Mustermann::Pattern
|
7
|
+
# @abstract
|
8
|
+
class RegexpBased < Pattern
|
9
|
+
# @return [Regexp] regular expression equivalent to the pattern.
|
10
|
+
attr_reader :regexp
|
11
|
+
alias_method :to_regexp, :regexp
|
12
|
+
|
13
|
+
# @param (see Mustermann::Pattern#initialize)
|
14
|
+
# @return (see Mustermann::Pattern#initialize)
|
15
|
+
# @see (see Mustermann::Pattern#initialize)
|
16
|
+
def initialize(string, options = {})
|
17
|
+
super
|
18
|
+
@regexp = compile(options)
|
19
|
+
end
|
20
|
+
|
21
|
+
extend Forwardable
|
22
|
+
def_delegators :regexp, :===, :=~, :match, :names, :named_captures
|
23
|
+
|
24
|
+
def compile(options = {})
|
25
|
+
raise NotImplementedError, 'subclass responsibility'
|
26
|
+
end
|
27
|
+
|
28
|
+
private :compile
|
29
|
+
end
|
30
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
require 'mustermann/regexp_based'
|
2
|
+
|
3
|
+
module Mustermann
|
4
|
+
# Regexp pattern implementation.
|
5
|
+
#
|
6
|
+
# @example
|
7
|
+
# Mustermann.new('/.*', type: :regexp) === '/bar' # => true
|
8
|
+
#
|
9
|
+
# @see Mustermann::Pattern
|
10
|
+
# @see file:README.md#simple Syntax description in the README
|
11
|
+
class Regular < RegexpBased
|
12
|
+
# @param (see Mustermann::Pattern#initialize)
|
13
|
+
# @return (see Mustermann::Pattern#initialize)
|
14
|
+
# @see (see Mustermann::Pattern#initialize)
|
15
|
+
def initialize(string, options = {})
|
16
|
+
string = $1 if string.to_s =~ /\A\(\?\-mix\:(.*)\)\Z/ && string.inspect == "/#$1/"
|
17
|
+
super(string, options)
|
18
|
+
end
|
19
|
+
|
20
|
+
def compile(options = {})
|
21
|
+
/\A#{@string}\Z/
|
22
|
+
end
|
23
|
+
|
24
|
+
private :compile
|
25
|
+
end
|
26
|
+
end
|
@@ -0,0 +1,50 @@
|
|
1
|
+
require 'mustermann/router/simple'
|
2
|
+
|
3
|
+
module Mustermann
|
4
|
+
module Router
|
5
|
+
# Simple pattern based router that allows matching paths to a given Rack application.
|
6
|
+
#
|
7
|
+
# @example config.ru
|
8
|
+
# router = Mustermann::Rack.new do
|
9
|
+
# on '/' do |env|
|
10
|
+
# [200, {'Content-Type' => 'text/plain'}, ['Hello World!']]
|
11
|
+
# end
|
12
|
+
#
|
13
|
+
# on '/:name' do |env|
|
14
|
+
# name = env['mustermann.params']['name']
|
15
|
+
# [200, {'Content-Type' => 'text/plain'}, ["Hello #{name}!"]]
|
16
|
+
# end
|
17
|
+
#
|
18
|
+
# on '/something/*', call: SomeApp
|
19
|
+
# end
|
20
|
+
#
|
21
|
+
# # in a config.ru
|
22
|
+
# run router
|
23
|
+
class Rack < Simple
|
24
|
+
def initialize(options = {}, &block)
|
25
|
+
env_prefix = options.delete(:env_prefix) || "mustermann"
|
26
|
+
params_key = options.delete(:params_key) || "#{env_prefix}.params"
|
27
|
+
pattern_key = options.delete(:pattern_key) || "#{env_prefix}.pattern"
|
28
|
+
@params_key, @pattern_key = params_key, pattern_key
|
29
|
+
options[:default] = [404, {"Content-Type" => "text/plain", "X-Cascade" => "pass"}, ["Not Found"]] unless options.include? :default
|
30
|
+
super(options, &block)
|
31
|
+
end
|
32
|
+
|
33
|
+
def invoke(callback, env, params, pattern)
|
34
|
+
params_was, pattern_was = env[@params_key], env[@pattern_key]
|
35
|
+
env[@params_key], env[@pattern_key] = params, pattern
|
36
|
+
response = callback.call(env)
|
37
|
+
response[1].each { |k,v| throw :pass if k.downcase == 'x-cascade' and v == 'pass' }
|
38
|
+
response
|
39
|
+
ensure
|
40
|
+
env[@params_key], env[@pattern_key] = params_was, pattern_was
|
41
|
+
end
|
42
|
+
|
43
|
+
def string_for(env)
|
44
|
+
env['PATH_INFO']
|
45
|
+
end
|
46
|
+
|
47
|
+
private :invoke, :string_for
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|