mustermann19 0.3.1
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.
- 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
|