rack-mount 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- data/LICENSE +20 -0
- data/README.rdoc +28 -0
- data/lib/rack/mount/analysis/frequency.rb +51 -0
- data/lib/rack/mount/analysis/histogram.rb +25 -0
- data/lib/rack/mount/analysis/splitting.rb +145 -0
- data/lib/rack/mount/const.rb +45 -0
- data/lib/rack/mount/exceptions.rb +3 -0
- data/lib/rack/mount/generatable_regexp.rb +163 -0
- data/lib/rack/mount/generation/route.rb +57 -0
- data/lib/rack/mount/generation/route_set.rb +163 -0
- data/lib/rack/mount/meta_method.rb +104 -0
- data/lib/rack/mount/mixover.rb +47 -0
- data/lib/rack/mount/multimap.rb +94 -0
- data/lib/rack/mount/prefix.rb +31 -0
- data/lib/rack/mount/recognition/code_generation.rb +99 -0
- data/lib/rack/mount/recognition/route.rb +59 -0
- data/lib/rack/mount/recognition/route_set.rb +88 -0
- data/lib/rack/mount/regexp_with_named_groups.rb +49 -0
- data/lib/rack/mount/route.rb +69 -0
- data/lib/rack/mount/route_set.rb +109 -0
- data/lib/rack/mount/strexp.rb +93 -0
- data/lib/rack/mount/utils.rb +271 -0
- data/lib/rack/mount/vendor/multimap/multimap.rb +466 -0
- data/lib/rack/mount/vendor/multimap/multiset.rb +153 -0
- data/lib/rack/mount/vendor/multimap/nested_multimap.rb +156 -0
- data/lib/rack/mount.rb +35 -0
- metadata +100 -0
data/LICENSE
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright (c) 2009 Joshua Peek
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
4
|
+
a copy of this software and associated documentation files (the
|
5
|
+
"Software"), to deal in the Software without restriction, including
|
6
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
7
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
8
|
+
permit persons to whom the Software is furnished to do so, subject to
|
9
|
+
the following conditions:
|
10
|
+
|
11
|
+
The above copyright notice and this permission notice shall be
|
12
|
+
included in all copies or substantial portions of the Software.
|
13
|
+
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
15
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
16
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
17
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
18
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
19
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
20
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.rdoc
ADDED
@@ -0,0 +1,28 @@
|
|
1
|
+
= Rack::Mount
|
2
|
+
|
3
|
+
A stackable dynamic tree based Rack router.
|
4
|
+
|
5
|
+
Rack::Mount supports Rack's Cascade style of trying several routes until it finds one that is not a 404. This allows multiple routes to be nested or stacked on top of each other. Since the application endpoint can trigger the router to continue matching, middleware can be used to add arbitrary conditions to any route. This allows you to route based on other request attributes, session information, or even data dynamically pulled from a database.
|
6
|
+
|
7
|
+
=== Usage
|
8
|
+
|
9
|
+
Rack::Mount provides a plugin API to build custom DSLs on top of.
|
10
|
+
|
11
|
+
The API is extremely minimal and only 3 methods are exposed as the public API.
|
12
|
+
|
13
|
+
<tt>Rack::Mount::RouteSet#add_route</tt>:: builder method for adding routes to the set
|
14
|
+
<tt>Rack::Mount::RouteSet#call</tt>:: Rack compatible recognition and dispatching method
|
15
|
+
<tt>Rack::Mount::RouteSet#url</tt>:: generates path from identifiers or significant keys
|
16
|
+
|
17
|
+
=== Example
|
18
|
+
|
19
|
+
require 'rack/mount'
|
20
|
+
Routes = Rack::Mount::RouteSet.new do |set|
|
21
|
+
# add_route takes a rack application and conditions to match with
|
22
|
+
# conditions may be strings or regexps
|
23
|
+
# See Rack::Mount::RouteSet#add_route for more options.
|
24
|
+
set.add_route FooApp, :method => 'get' :path => %{/foo}
|
25
|
+
end
|
26
|
+
|
27
|
+
# The route set itself is a simple rack app you mount
|
28
|
+
run Routes
|
@@ -0,0 +1,51 @@
|
|
1
|
+
module Rack::Mount
|
2
|
+
module Analysis
|
3
|
+
class Frequency #:nodoc:
|
4
|
+
extend Mixover
|
5
|
+
|
6
|
+
def initialize(*keys)
|
7
|
+
clear
|
8
|
+
keys.each { |key| self << key }
|
9
|
+
end
|
10
|
+
|
11
|
+
def clear
|
12
|
+
@raw_keys = []
|
13
|
+
@key_frequency = Analysis::Histogram.new
|
14
|
+
self
|
15
|
+
end
|
16
|
+
|
17
|
+
def <<(key)
|
18
|
+
raise ArgumentError unless key.is_a?(Hash)
|
19
|
+
@raw_keys << key
|
20
|
+
nil
|
21
|
+
end
|
22
|
+
|
23
|
+
def possible_keys
|
24
|
+
@possible_keys ||= begin
|
25
|
+
@raw_keys.map do |key|
|
26
|
+
key.inject({}) { |requirements, (method, requirement)|
|
27
|
+
process_key(requirements, method, requirement)
|
28
|
+
requirements
|
29
|
+
}
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
def process_key(requirements, method, requirement)
|
35
|
+
if requirement.is_a?(Regexp)
|
36
|
+
requirements[method] = Utils.extract_static_regexp(requirement)
|
37
|
+
else
|
38
|
+
requirements[method] = requirement
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
def report
|
43
|
+
@report ||= begin
|
44
|
+
possible_keys.each { |keys| keys.each_pair { |key, _| @key_frequency << key } }
|
45
|
+
return [] if @key_frequency.count <= 1
|
46
|
+
@key_frequency.select_upper
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
module Rack::Mount
|
2
|
+
module Analysis
|
3
|
+
class Histogram < Hash #:nodoc:
|
4
|
+
attr_reader :count
|
5
|
+
|
6
|
+
def initialize
|
7
|
+
@count = 0
|
8
|
+
super(0)
|
9
|
+
end
|
10
|
+
|
11
|
+
def <<(value)
|
12
|
+
@count += 1
|
13
|
+
self[value] += 1 if value
|
14
|
+
end
|
15
|
+
|
16
|
+
def select_upper
|
17
|
+
values = sort_by { |_, value| value }
|
18
|
+
values.reverse!
|
19
|
+
values = values.select { |_, value| value >= count / size }
|
20
|
+
values.map! { |key, _| key }
|
21
|
+
values
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,145 @@
|
|
1
|
+
module Rack::Mount
|
2
|
+
module Analysis
|
3
|
+
module Splitting
|
4
|
+
class Key < Array
|
5
|
+
def initialize(method, index, separators)
|
6
|
+
replace([method, index, separators])
|
7
|
+
end
|
8
|
+
|
9
|
+
def self.split(value, separator_pattern)
|
10
|
+
keys = value.split(separator_pattern)
|
11
|
+
keys.shift if keys[0] == Const::EMPTY_STRING
|
12
|
+
keys << Const::NULL
|
13
|
+
keys
|
14
|
+
end
|
15
|
+
|
16
|
+
def call(cache, obj)
|
17
|
+
(cache[self[0]] ||= self.class.split(obj.send(self[0]), self[2]))[self[1]]
|
18
|
+
end
|
19
|
+
|
20
|
+
def call_source(cache, obj)
|
21
|
+
"(#{cache}[:#{self[0]}] ||= Analysis::Splitting::Key.split(#{obj}.#{self[0]}, #{self[2].inspect}))[#{self[1]}]"
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
def clear
|
26
|
+
@boundaries = {}
|
27
|
+
super
|
28
|
+
end
|
29
|
+
|
30
|
+
def <<(key)
|
31
|
+
super
|
32
|
+
key.each_pair do |k, v|
|
33
|
+
analyze_capture_boundaries(v, @boundaries[k] ||= Histogram.new)
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
def separators(key)
|
38
|
+
@boundaries[key].select_upper
|
39
|
+
end
|
40
|
+
|
41
|
+
def process_key(requirements, method, requirement)
|
42
|
+
separators = separators(method)
|
43
|
+
if requirement.is_a?(Regexp) && separators.any?
|
44
|
+
generate_split_keys(requirement, separators).each_with_index do |value, index|
|
45
|
+
requirements[Key.new(method, index, Regexp.union(*separators))] = value
|
46
|
+
end
|
47
|
+
else
|
48
|
+
super
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
private
|
53
|
+
def analyze_capture_boundaries(regexp, boundaries) #:nodoc:
|
54
|
+
return boundaries unless regexp.is_a?(Regexp)
|
55
|
+
|
56
|
+
parts = Utils.extract_regexp_parts(regexp) rescue []
|
57
|
+
parts.each_with_index do |part, index|
|
58
|
+
break if part == Const::NULL
|
59
|
+
|
60
|
+
if index > 0
|
61
|
+
previous = parts[index-1]
|
62
|
+
if previous.is_a?(Utils::Capture)
|
63
|
+
previous = Utils.extract_static_regexp(previous.last_part) rescue nil
|
64
|
+
end
|
65
|
+
boundaries << previous[-1..-1] if previous.is_a?(String)
|
66
|
+
end
|
67
|
+
|
68
|
+
if index < parts.length
|
69
|
+
following = parts[index+1]
|
70
|
+
if following.is_a?(Utils::Capture)
|
71
|
+
following = Utils.extract_static_regexp(following.first_part) rescue nil
|
72
|
+
end
|
73
|
+
if following.is_a?(String) && following != Const::NULL
|
74
|
+
boundaries << following[0..0] == '\\' ? following[1..1] : following[0..0]
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
78
|
+
boundaries
|
79
|
+
end
|
80
|
+
|
81
|
+
def generate_split_keys(regexp, separators) #:nodoc:
|
82
|
+
escaped_separators = separators.map { |s| Regexp.escape(s) }
|
83
|
+
separators_regexp = Regexp.union(*escaped_separators)
|
84
|
+
segments, previous = [], nil
|
85
|
+
regexp_options = regexp.options
|
86
|
+
|
87
|
+
begin
|
88
|
+
Utils.extract_regexp_parts(regexp).each do |part|
|
89
|
+
if part.respond_to?(:optional?) && part.optional?
|
90
|
+
if escaped_separators.include?(part.first)
|
91
|
+
append_to_segments!(segments, previous, separators, regexp_options)
|
92
|
+
end
|
93
|
+
|
94
|
+
raise ArgumentError
|
95
|
+
end
|
96
|
+
|
97
|
+
append_to_segments!(segments, previous, separators, regexp_options)
|
98
|
+
previous = nil
|
99
|
+
|
100
|
+
if part == Const::NULL
|
101
|
+
segments << Const::NULL
|
102
|
+
raise ArgumentError
|
103
|
+
end
|
104
|
+
|
105
|
+
if part.is_a?(Utils::Capture)
|
106
|
+
source = part.map { |p| p.to_s }.join
|
107
|
+
append_to_segments!(segments, source, separators, regexp_options)
|
108
|
+
else
|
109
|
+
parts = part.split(separators_regexp)
|
110
|
+
parts.shift if parts[0] == Const::EMPTY_STRING
|
111
|
+
previous = parts.pop
|
112
|
+
parts.each { |p| append_to_segments!(segments, p, separators, regexp_options) }
|
113
|
+
end
|
114
|
+
end
|
115
|
+
|
116
|
+
append_to_segments!(segments, previous, separators, regexp_options)
|
117
|
+
rescue ArgumentError
|
118
|
+
# generation failed somewhere, but lets take what we can get
|
119
|
+
end
|
120
|
+
|
121
|
+
while segments.length > 0 && (segments.last.nil? || segments.last == '')
|
122
|
+
segments.pop
|
123
|
+
end
|
124
|
+
|
125
|
+
segments
|
126
|
+
end
|
127
|
+
|
128
|
+
def append_to_segments!(segments, s, separators, regexp_options) #:nodoc:
|
129
|
+
return unless s
|
130
|
+
separators.each do |separator|
|
131
|
+
if s.gsub(/\[[^\]]+\]/, '').include?(separator)
|
132
|
+
raise ArgumentError
|
133
|
+
end
|
134
|
+
|
135
|
+
if Regexp.compile("\\A#{s}\\Z") =~ separator
|
136
|
+
raise ArgumentError
|
137
|
+
end
|
138
|
+
end
|
139
|
+
|
140
|
+
static = Utils.extract_static_regexp(s, regexp_options)
|
141
|
+
segments << (static.is_a?(String) ? static : static)
|
142
|
+
end
|
143
|
+
end
|
144
|
+
end
|
145
|
+
end
|
@@ -0,0 +1,45 @@
|
|
1
|
+
module Rack::Mount
|
2
|
+
module Const
|
3
|
+
RACK_ROUTING_ARGS = 'rack.routing_args'.freeze
|
4
|
+
|
5
|
+
begin
|
6
|
+
eval('/(?<foo>.*)/').named_captures
|
7
|
+
SUPPORTS_NAMED_CAPTURES = true
|
8
|
+
REGEXP_NAMED_CAPTURE = '(?<%s>%s)'.freeze
|
9
|
+
rescue SyntaxError, NoMethodError
|
10
|
+
SUPPORTS_NAMED_CAPTURES = false
|
11
|
+
REGEXP_NAMED_CAPTURE = '(?:<%s>%s)'.freeze
|
12
|
+
end
|
13
|
+
|
14
|
+
EMPTY_ARRAY = [].freeze
|
15
|
+
EMPTY_HASH = {}.freeze
|
16
|
+
|
17
|
+
NULL = "\0".freeze
|
18
|
+
|
19
|
+
CONTENT_TYPE = 'Content-Type'.freeze
|
20
|
+
CONTINUE = '100-continue'.freeze
|
21
|
+
DELETE = 'PUT'.freeze
|
22
|
+
EMPTY_STRING = ''.freeze
|
23
|
+
EXPECT = 'Expect'.freeze
|
24
|
+
GET = 'GET'.freeze
|
25
|
+
HEAD = 'HEAD'.freeze
|
26
|
+
PATH_INFO = 'PATH_INFO'.freeze
|
27
|
+
POST = 'POST'.freeze
|
28
|
+
PUT = 'PUT'.freeze
|
29
|
+
REQUEST_METHOD = 'REQUEST_METHOD'.freeze
|
30
|
+
SCRIPT_NAME = 'SCRIPT_NAME'.freeze
|
31
|
+
SLASH = '/'.freeze
|
32
|
+
TEXT_SLASH_HTML = 'text/html'.freeze
|
33
|
+
|
34
|
+
DEFAULT_CONTENT_TYPE_HEADERS = {CONTENT_TYPE => TEXT_SLASH_HTML}.freeze
|
35
|
+
HTTP_METHODS = [GET, HEAD, POST, PUT, DELETE].freeze
|
36
|
+
|
37
|
+
OK = 'OK'.freeze
|
38
|
+
NOT_FOUND = 'Not Found'.freeze
|
39
|
+
EXPECTATION_FAILED = 'Expectation failed'.freeze
|
40
|
+
|
41
|
+
OK_RESPONSE = [200, DEFAULT_CONTENT_TYPE_HEADERS, [OK].freeze].freeze
|
42
|
+
NOT_FOUND_RESPONSE = [404, DEFAULT_CONTENT_TYPE_HEADERS, [NOT_FOUND].freeze].freeze
|
43
|
+
EXPECTATION_FAILED_RESPONSE = [417, DEFAULT_CONTENT_TYPE_HEADERS, [EXPECTATION_FAILED].freeze].freeze
|
44
|
+
end
|
45
|
+
end
|
@@ -0,0 +1,163 @@
|
|
1
|
+
require 'rack/mount/utils'
|
2
|
+
|
3
|
+
module Rack::Mount
|
4
|
+
class GeneratableRegexp < Regexp #:nodoc:
|
5
|
+
class DynamicSegment #:nodoc:
|
6
|
+
attr_reader :name, :requirement
|
7
|
+
|
8
|
+
def initialize(name, requirement)
|
9
|
+
@name, @requirement = name.to_sym, bound_expression(requirement)
|
10
|
+
freeze
|
11
|
+
end
|
12
|
+
|
13
|
+
def ==(obj)
|
14
|
+
@name == obj.name && @requirement == obj.requirement
|
15
|
+
end
|
16
|
+
|
17
|
+
def =~(str)
|
18
|
+
@requirement =~ str
|
19
|
+
end
|
20
|
+
|
21
|
+
def to_hash
|
22
|
+
{ @name => @requirement }
|
23
|
+
end
|
24
|
+
|
25
|
+
def inspect
|
26
|
+
"/(?<#{@name}>#{@requirement.source})/"
|
27
|
+
end
|
28
|
+
|
29
|
+
private
|
30
|
+
def bound_expression(regexp)
|
31
|
+
source, options = regexp.source, regexp.options
|
32
|
+
source = "\\A#{source}\\Z"
|
33
|
+
Regexp.compile(source, options).freeze
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
module InstanceMethods
|
38
|
+
def self.extended(obj)
|
39
|
+
obj.segments
|
40
|
+
end
|
41
|
+
|
42
|
+
def generatable?
|
43
|
+
segments.any?
|
44
|
+
end
|
45
|
+
|
46
|
+
def generate(params = {}, recall = {}, defaults = {})
|
47
|
+
merged = recall.merge(params)
|
48
|
+
generate_from_segments(segments, params, merged, defaults)
|
49
|
+
end
|
50
|
+
|
51
|
+
def segments
|
52
|
+
@segments ||= begin
|
53
|
+
parse_segments(Utils.extract_regexp_parts(self))
|
54
|
+
rescue ArgumentError
|
55
|
+
Const::EMPTY_ARRAY
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
def captures
|
60
|
+
segments.flatten.find_all { |s| s.is_a?(DynamicSegment) }
|
61
|
+
end
|
62
|
+
|
63
|
+
def required_captures
|
64
|
+
segments.find_all { |s| s.is_a?(DynamicSegment) }
|
65
|
+
end
|
66
|
+
|
67
|
+
private
|
68
|
+
def parse_segments(segments)
|
69
|
+
s = []
|
70
|
+
segments.each do |part|
|
71
|
+
if part.is_a?(String) && part == Const::NULL
|
72
|
+
return s
|
73
|
+
elsif part.is_a?(Utils::Capture)
|
74
|
+
if part.named?
|
75
|
+
source = part.map { |p| p.to_s }.join
|
76
|
+
requirement = Regexp.compile(source)
|
77
|
+
s << DynamicSegment.new(part.name, requirement)
|
78
|
+
else
|
79
|
+
s << parse_segments(part)
|
80
|
+
end
|
81
|
+
else
|
82
|
+
part = part.gsub('\\/', '/')
|
83
|
+
static = Utils.extract_static_regexp(part)
|
84
|
+
if static.is_a?(String)
|
85
|
+
s << static.freeze
|
86
|
+
else
|
87
|
+
raise ArgumentError, "failed to parse #{part.inspect}"
|
88
|
+
end
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
s.freeze
|
93
|
+
end
|
94
|
+
|
95
|
+
def generate_from_segments(segments, params, merged, defaults, optional = false)
|
96
|
+
if optional
|
97
|
+
return Const::EMPTY_STRING if segments.all? { |s| s.is_a?(String) }
|
98
|
+
return Const::EMPTY_STRING unless segments.flatten.any? { |s|
|
99
|
+
params[s.name] if s.is_a?(DynamicSegment)
|
100
|
+
}
|
101
|
+
return Const::EMPTY_STRING if segments.any? { |segment|
|
102
|
+
if segment.is_a?(DynamicSegment)
|
103
|
+
value = merged[segment.name] || defaults[segment.name]
|
104
|
+
value = value.to_param if value.respond_to?(:to_param)
|
105
|
+
|
106
|
+
merged_value = merged[segment.name]
|
107
|
+
merged_value = merged_value.to_param if merged_value.respond_to?(:to_param)
|
108
|
+
|
109
|
+
default_value = defaults[segment.name]
|
110
|
+
default_value = default_value.to_param if default_value.respond_to?(:to_param)
|
111
|
+
|
112
|
+
if value.nil? || segment !~ value
|
113
|
+
true
|
114
|
+
elsif merged_value == default_value
|
115
|
+
# Nasty control flow
|
116
|
+
return :clear_remaining_segments
|
117
|
+
true
|
118
|
+
else
|
119
|
+
false
|
120
|
+
end
|
121
|
+
end
|
122
|
+
}
|
123
|
+
end
|
124
|
+
|
125
|
+
generated = segments.map do |segment|
|
126
|
+
case segment
|
127
|
+
when String
|
128
|
+
segment
|
129
|
+
when DynamicSegment
|
130
|
+
value = params[segment.name] || merged[segment.name] || defaults[segment.name]
|
131
|
+
value = value.to_param if value.respond_to?(:to_param)
|
132
|
+
if value && segment =~ value.to_s
|
133
|
+
value
|
134
|
+
else
|
135
|
+
return
|
136
|
+
end
|
137
|
+
when Array
|
138
|
+
value = generate_from_segments(segment, params, merged, defaults, true)
|
139
|
+
if value == :clear_remaining_segments
|
140
|
+
segment.each { |s| params.delete(s.name) if s.is_a?(DynamicSegment) }
|
141
|
+
Const::EMPTY_STRING
|
142
|
+
elsif value.nil?
|
143
|
+
Const::EMPTY_STRING
|
144
|
+
else
|
145
|
+
value
|
146
|
+
end
|
147
|
+
end
|
148
|
+
end
|
149
|
+
|
150
|
+
# Delete any used items from the params
|
151
|
+
segments.each { |s| params.delete(s.name) if s.is_a?(DynamicSegment) }
|
152
|
+
|
153
|
+
generated.join
|
154
|
+
end
|
155
|
+
end
|
156
|
+
include InstanceMethods
|
157
|
+
|
158
|
+
def initialize(regexp)
|
159
|
+
super
|
160
|
+
segments
|
161
|
+
end
|
162
|
+
end
|
163
|
+
end
|
@@ -0,0 +1,57 @@
|
|
1
|
+
require 'rack/mount/utils'
|
2
|
+
|
3
|
+
module Rack::Mount
|
4
|
+
module Generation
|
5
|
+
module Route #:nodoc:
|
6
|
+
attr_reader :generation_keys
|
7
|
+
|
8
|
+
def initialize(*args)
|
9
|
+
super
|
10
|
+
|
11
|
+
@required_params = {}
|
12
|
+
@required_defaults = {}
|
13
|
+
@generation_keys = @defaults.dup
|
14
|
+
|
15
|
+
@conditions.each do |method, condition|
|
16
|
+
@required_params[method] = @conditions[method].required_captures.map { |s| s.name }.reject { |s| @defaults.include?(s) }.freeze
|
17
|
+
@required_defaults[method] = @defaults.dup
|
18
|
+
@conditions[method].captures.inject({}) { |h, s| h.merge!(s.to_hash) }.keys.each { |name|
|
19
|
+
@required_defaults[method].delete(name)
|
20
|
+
@generation_keys.delete(name) if @defaults.include?(name)
|
21
|
+
}
|
22
|
+
@required_defaults[method].freeze
|
23
|
+
end
|
24
|
+
|
25
|
+
@required_params.freeze
|
26
|
+
@required_defaults.freeze
|
27
|
+
@generation_keys.freeze
|
28
|
+
end
|
29
|
+
|
30
|
+
def generate(methods, params = {}, recall = {})
|
31
|
+
if methods.is_a?(Array)
|
32
|
+
result = methods.map { |m| generate_method(m, params, recall, @defaults) || (return nil) }
|
33
|
+
else
|
34
|
+
result = generate_method(methods, params, recall, @defaults)
|
35
|
+
end
|
36
|
+
|
37
|
+
if result
|
38
|
+
@defaults.each do |key, value|
|
39
|
+
params.delete(key) if params[key] == value
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
result
|
44
|
+
end
|
45
|
+
|
46
|
+
private
|
47
|
+
def generate_method(method, params, recall, defaults)
|
48
|
+
merged = recall.merge(params)
|
49
|
+
return nil unless condition = @conditions[method]
|
50
|
+
return nil if condition.segments.empty?
|
51
|
+
return nil unless @required_params[method].all? { |p| merged.include?(p) }
|
52
|
+
return nil unless @required_defaults[method].all? { |k, v| merged[k] == v }
|
53
|
+
condition.generate(params, recall, defaults)
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|