lgierth-rack-mount 0.6.13

Sign up to get free protection for your applications and to get access to all the features.
Files changed (37) hide show
  1. data/LICENSE +20 -0
  2. data/README.rdoc +36 -0
  3. data/lib/rack/mount.rb +32 -0
  4. data/lib/rack/mount/analysis/frequency.rb +60 -0
  5. data/lib/rack/mount/analysis/histogram.rb +74 -0
  6. data/lib/rack/mount/analysis/splitting.rb +159 -0
  7. data/lib/rack/mount/code_generation.rb +117 -0
  8. data/lib/rack/mount/generatable_regexp.rb +210 -0
  9. data/lib/rack/mount/multimap.rb +53 -0
  10. data/lib/rack/mount/prefix.rb +36 -0
  11. data/lib/rack/mount/regexp_with_named_groups.rb +69 -0
  12. data/lib/rack/mount/route.rb +130 -0
  13. data/lib/rack/mount/route_set.rb +420 -0
  14. data/lib/rack/mount/strexp.rb +68 -0
  15. data/lib/rack/mount/strexp/parser.rb +160 -0
  16. data/lib/rack/mount/strexp/parser.y +34 -0
  17. data/lib/rack/mount/strexp/tokenizer.rb +83 -0
  18. data/lib/rack/mount/strexp/tokenizer.rex +12 -0
  19. data/lib/rack/mount/utils.rb +162 -0
  20. data/lib/rack/mount/vendor/multimap/multimap.rb +569 -0
  21. data/lib/rack/mount/vendor/multimap/multiset.rb +185 -0
  22. data/lib/rack/mount/vendor/multimap/nested_multimap.rb +158 -0
  23. data/lib/rack/mount/vendor/regin/regin.rb +75 -0
  24. data/lib/rack/mount/vendor/regin/regin/alternation.rb +40 -0
  25. data/lib/rack/mount/vendor/regin/regin/anchor.rb +4 -0
  26. data/lib/rack/mount/vendor/regin/regin/atom.rb +54 -0
  27. data/lib/rack/mount/vendor/regin/regin/character.rb +51 -0
  28. data/lib/rack/mount/vendor/regin/regin/character_class.rb +50 -0
  29. data/lib/rack/mount/vendor/regin/regin/collection.rb +77 -0
  30. data/lib/rack/mount/vendor/regin/regin/expression.rb +126 -0
  31. data/lib/rack/mount/vendor/regin/regin/group.rb +85 -0
  32. data/lib/rack/mount/vendor/regin/regin/options.rb +55 -0
  33. data/lib/rack/mount/vendor/regin/regin/parser.rb +520 -0
  34. data/lib/rack/mount/vendor/regin/regin/tokenizer.rb +246 -0
  35. data/lib/rack/mount/vendor/regin/regin/version.rb +3 -0
  36. data/lib/rack/mount/version.rb +3 -0
  37. metadata +140 -0
@@ -0,0 +1,210 @@
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, 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
+ end
29
+
30
+ module InstanceMethods
31
+ def self.extended(obj)
32
+ obj.segments
33
+ end
34
+
35
+ def defaults=(defaults)
36
+ @required_captures = nil
37
+ @required_params = nil
38
+ @required_defaults = nil
39
+ @defaults = defaults
40
+ end
41
+
42
+ def defaults
43
+ @defaults ||= {}
44
+ end
45
+
46
+ def generatable?
47
+ segments.any?
48
+ end
49
+
50
+ def generate(params = {}, recall = {}, options = {})
51
+ return nil unless generatable?
52
+
53
+ merged = recall.merge(params)
54
+ return nil unless required_params.all? { |p| merged.include?(p) }
55
+ return nil unless required_defaults.all? { |k, v| merged[k] == v }
56
+
57
+ generate_from_segments(segments, params, merged, options)
58
+ end
59
+
60
+ def segments
61
+ @segments ||= begin
62
+ defaults
63
+ segments = []
64
+ catch(:halt) do
65
+ expression = Utils.parse_regexp(self)
66
+ segments = parse_segments(expression)
67
+ end
68
+ segments
69
+ end
70
+ end
71
+
72
+ def captures
73
+ segments.flatten.find_all { |s| s.is_a?(DynamicSegment) }
74
+ end
75
+
76
+ def required_captures
77
+ @required_captures ||= segments.find_all { |s|
78
+ s.is_a?(DynamicSegment) && !@defaults.include?(s.name)
79
+ }.freeze
80
+ end
81
+
82
+ def required_params
83
+ @required_params ||= required_captures.map { |s| s.name }.freeze
84
+ end
85
+
86
+ def required_defaults
87
+ @required_defaults ||= begin
88
+ required_defaults = @defaults.dup
89
+ captures.inject({}) { |h, s| h.merge!(s.to_hash) }.keys.each { |name|
90
+ required_defaults.delete(name)
91
+ }
92
+ required_defaults
93
+ end
94
+ end
95
+
96
+ def freeze
97
+ segments
98
+ captures
99
+ required_captures
100
+ required_params
101
+ required_defaults
102
+ super
103
+ end
104
+
105
+ private
106
+ def parse_segments(segments)
107
+ s = []
108
+ segments.each_with_index do |part, index|
109
+ case part
110
+ when Regin::Anchor
111
+ # ignore
112
+ when Regin::Character
113
+ throw :halt unless part.literal?
114
+
115
+ if s.last.is_a?(String)
116
+ s.last << part.value.dup
117
+ else
118
+ s << part.value.dup
119
+ end
120
+ when Regin::Group
121
+ if part.name
122
+ s << DynamicSegment.new(part.name, part.expression.to_regexp(true))
123
+ else
124
+ s << parse_segments(part.expression)
125
+ end
126
+ when Regin::Expression
127
+ return parse_segments(part)
128
+ else
129
+ throw :halt
130
+ end
131
+ end
132
+
133
+ s
134
+ end
135
+
136
+ EMPTY_STRING = ''.freeze
137
+
138
+ def generate_from_segments(segments, params, merged, options, optional = false)
139
+ if optional
140
+ return EMPTY_STRING if segments.all? { |s| s.is_a?(String) }
141
+ return EMPTY_STRING unless segments.flatten.any? { |s|
142
+ params.has_key?(s.name) if s.is_a?(DynamicSegment)
143
+ }
144
+ return EMPTY_STRING if segments.any? { |segment|
145
+ if segment.is_a?(DynamicSegment)
146
+ value = merged[segment.name] || @defaults[segment.name]
147
+ value = parameterize(segment.name, value, options)
148
+
149
+ merged_value = parameterize(segment.name, merged[segment.name], options)
150
+ default_value = parameterize(segment.name, @defaults[segment.name], options)
151
+
152
+ if value.nil? || segment !~ value
153
+ true
154
+ elsif merged_value == default_value
155
+ # Nasty control flow
156
+ return :clear_remaining_segments
157
+ else
158
+ false
159
+ end
160
+ end
161
+ }
162
+ end
163
+
164
+ generated = segments.map do |segment|
165
+ case segment
166
+ when String
167
+ segment
168
+ when DynamicSegment
169
+ value = params[segment.name] || merged[segment.name] || @defaults[segment.name]
170
+ value = parameterize(segment.name, value, options)
171
+ if value && segment =~ value.to_s
172
+ value
173
+ else
174
+ return
175
+ end
176
+ when Array
177
+ value = generate_from_segments(segment, params, merged, options, true)
178
+ if value == :clear_remaining_segments
179
+ segment.each { |s| params.delete(s.name) if s.is_a?(DynamicSegment) }
180
+ EMPTY_STRING
181
+ elsif value.nil?
182
+ EMPTY_STRING
183
+ else
184
+ value
185
+ end
186
+ end
187
+ end
188
+
189
+ # Delete any used items from the params
190
+ segments.each { |s| params.delete(s.name) if s.is_a?(DynamicSegment) }
191
+
192
+ generated.join
193
+ end
194
+
195
+ def parameterize(name, value, options)
196
+ if block = options[:parameterize]
197
+ block.call(name, value)
198
+ else
199
+ value
200
+ end
201
+ end
202
+ end
203
+ include InstanceMethods
204
+
205
+ def initialize(regexp)
206
+ super
207
+ segments
208
+ end
209
+ end
210
+ end
@@ -0,0 +1,53 @@
1
+ begin
2
+ require 'nested_multimap'
3
+ rescue LoadError
4
+ $: << File.expand_path(File.join(File.dirname(__FILE__), 'vendor/multimap'))
5
+ require 'nested_multimap'
6
+ end
7
+
8
+ module Rack::Mount
9
+ class Multimap < NestedMultimap #:nodoc:
10
+ def store(*args)
11
+ keys = args.dup
12
+ value = keys.pop
13
+ key = keys.shift
14
+
15
+ raise ArgumentError, 'wrong number of arguments (1 for 2)' unless value
16
+
17
+ unless key.respond_to?(:=~)
18
+ raise ArgumentError, "unsupported key: #{args.first.inspect}"
19
+ end
20
+
21
+ if key.is_a?(Regexp)
22
+ if keys.empty?
23
+ @hash.each_pair { |k, l| l << value if k =~ key }
24
+ self.default << value
25
+ else
26
+ @hash.each_pair { |k, _|
27
+ if k =~ key
28
+ args[0] = k
29
+ super(*args)
30
+ end
31
+ }
32
+
33
+ self.default = self.class.new(default) unless default.is_a?(self.class)
34
+ default[*keys.dup] = value
35
+ end
36
+ else
37
+ super(*args)
38
+ end
39
+ end
40
+ alias_method :[]=, :store
41
+
42
+ undef :index, :invert
43
+
44
+ def height
45
+ containers_with_default.max { |a, b| a.length <=> b.length }.length
46
+ end
47
+
48
+ def average_height
49
+ lengths = containers_with_default.map { |e| e.length }
50
+ lengths.inject(0) { |sum, len| sum += len }.to_f / lengths.size
51
+ end
52
+ end
53
+ end
@@ -0,0 +1,36 @@
1
+ require 'rack/mount/utils'
2
+
3
+ module Rack::Mount
4
+ class Prefix #:nodoc:
5
+ EMPTY_STRING = ''.freeze
6
+ PATH_INFO = 'PATH_INFO'.freeze
7
+ SCRIPT_NAME = 'SCRIPT_NAME'.freeze
8
+ SLASH = '/'.freeze
9
+
10
+ KEY = 'rack.mount.prefix'.freeze
11
+
12
+ def initialize(app, prefix = nil)
13
+ @app, @prefix = app, prefix.freeze
14
+ freeze
15
+ end
16
+
17
+ def call(env)
18
+ if prefix = env[KEY] || @prefix
19
+ old_path_info = env[PATH_INFO].dup
20
+ old_script_name = env[SCRIPT_NAME].dup
21
+
22
+ begin
23
+ env[PATH_INFO] = env[PATH_INFO].sub(prefix, EMPTY_STRING)
24
+ env[PATH_INFO] = EMPTY_STRING if env[PATH_INFO] == SLASH
25
+ env[SCRIPT_NAME] = Utils.normalize_path(env[SCRIPT_NAME].to_s + prefix)
26
+ @app.call(env)
27
+ ensure
28
+ env[PATH_INFO] = old_path_info
29
+ env[SCRIPT_NAME] = old_script_name
30
+ end
31
+ else
32
+ @app.call(env)
33
+ end
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,69 @@
1
+ module Rack::Mount
2
+ if Regin.regexp_supports_named_captures?
3
+ RegexpWithNamedGroups = Regexp
4
+ else
5
+ require 'strscan'
6
+
7
+ # A wrapper that adds shim named capture support to older
8
+ # versions of Ruby.
9
+ #
10
+ # Because the named capture syntax causes a parse error, an
11
+ # alternate syntax is used to indicate named captures.
12
+ #
13
+ # Ruby 1.9+ named capture syntax:
14
+ #
15
+ # /(?<foo>[a-z]+)/
16
+ #
17
+ # Ruby 1.8 shim syntax:
18
+ #
19
+ # /(?:<foo>[a-z]+)/
20
+ class RegexpWithNamedGroups < Regexp
21
+ def self.new(regexp) #:nodoc:
22
+ if regexp.is_a?(RegexpWithNamedGroups)
23
+ regexp
24
+ else
25
+ super
26
+ end
27
+ end
28
+
29
+ # Wraps Regexp with named capture support.
30
+ def initialize(regexp)
31
+ regexp = Regexp.compile(regexp) unless regexp.is_a?(Regexp)
32
+ source, options = regexp.source, regexp.options
33
+ @names, scanner = [], StringScanner.new(source)
34
+
35
+ while scanner.skip_until(/\(/)
36
+ if scanner.scan(/\?:<([^>]+)>/)
37
+ @names << scanner[1]
38
+ elsif scanner.scan(/\?(i?m?x?\-?i?m?x?)?:/)
39
+ # ignore noncapture
40
+ else
41
+ @names << nil
42
+ end
43
+ end
44
+ source.gsub!(/\?:<([^>]+)>/, '')
45
+
46
+ @names = [] unless @names.any?
47
+ @names.freeze
48
+
49
+ super(source, options)
50
+ end
51
+
52
+ def names
53
+ @names.dup
54
+ end
55
+
56
+ def named_captures
57
+ named_captures = {}
58
+ names.each_with_index { |n, i|
59
+ named_captures[n] = [i+1] if n
60
+ }
61
+ named_captures
62
+ end
63
+
64
+ def eql?(other)
65
+ super && @names.eql?(other.names)
66
+ end
67
+ end
68
+ end
69
+ end
@@ -0,0 +1,130 @@
1
+ require 'rack/mount/generatable_regexp'
2
+ require 'rack/mount/regexp_with_named_groups'
3
+ require 'rack/mount/utils'
4
+
5
+ module Rack::Mount
6
+ # Route is an internal class used to wrap a single route attributes.
7
+ #
8
+ # Plugins should not depend on any method on this class or instantiate
9
+ # new Route objects. Instead use the factory method, RouteSet#add_route
10
+ # to create new routes and add them to the set.
11
+ class Route
12
+ # Valid rack application to call if conditions are met
13
+ attr_reader :app
14
+
15
+ # A hash of conditions to match against. Conditions may be expressed
16
+ # as strings or regexps to match against.
17
+ attr_reader :conditions
18
+
19
+ # A hash of values that always gets merged into the parameters hash
20
+ attr_reader :defaults
21
+
22
+ # Symbol identifier for the route used with named route generations
23
+ attr_reader :name
24
+
25
+ attr_reader :named_captures
26
+
27
+ def initialize(app, conditions, defaults, name)
28
+ unless app.respond_to?(:call)
29
+ raise ArgumentError, 'app must be a valid rack application' \
30
+ ' and respond to call'
31
+ end
32
+ @app = app
33
+
34
+ @name = name ? name.to_sym : nil
35
+ @defaults = (defaults || {}).freeze
36
+
37
+ @conditions = {}
38
+
39
+ conditions.each do |method, pattern|
40
+ next unless method && pattern
41
+
42
+ pattern = Regexp.compile("\\A#{Regexp.escape(pattern)}\\Z") if pattern.is_a?(String)
43
+
44
+ if pattern.is_a?(Regexp)
45
+ pattern = Utils.normalize_extended_expression(pattern)
46
+ pattern = RegexpWithNamedGroups.new(pattern)
47
+ pattern.extend(GeneratableRegexp::InstanceMethods)
48
+ pattern.defaults = @defaults
49
+ end
50
+
51
+ @conditions[method] = pattern.freeze
52
+ end
53
+
54
+ @named_captures = {}
55
+ @conditions.map { |method, condition|
56
+ next unless condition.respond_to?(:named_captures)
57
+ @named_captures[method] = condition.named_captures.inject({}) { |named_captures, (k, v)|
58
+ named_captures[k.to_sym] = v.last - 1
59
+ named_captures
60
+ }.freeze
61
+ }
62
+ @named_captures.freeze
63
+
64
+ @has_significant_params = @conditions.any? { |method, condition|
65
+ (condition.respond_to?(:required_params) && condition.required_params.any?) ||
66
+ (condition.respond_to?(:required_defaults) && condition.required_defaults.any?)
67
+ }
68
+
69
+ if @conditions.has_key?(:path_info) &&
70
+ !Utils.regexp_anchored?(@conditions[:path_info])
71
+ @prefix = true
72
+ @app = Prefix.new(@app)
73
+ else
74
+ @prefix = false
75
+ end
76
+
77
+ @conditions.freeze
78
+ end
79
+
80
+ def prefix?
81
+ @prefix
82
+ end
83
+
84
+
85
+ def generation_keys
86
+ @conditions.inject({}) { |keys, (method, condition)|
87
+ if condition.respond_to?(:required_defaults)
88
+ keys.merge!(condition.required_defaults)
89
+ else
90
+ keys
91
+ end
92
+ }
93
+ end
94
+
95
+ def significant_params?
96
+ @has_significant_params
97
+ end
98
+
99
+ def generate(method, params = {}, recall = {}, options = {})
100
+ if method.nil?
101
+ result = @conditions.inject({}) { |h, (m, condition)|
102
+ if condition.respond_to?(:generate)
103
+ h[m] = condition.generate(params, recall, options)
104
+ end
105
+ h
106
+ }
107
+ return nil if result.values.compact.empty?
108
+ else
109
+ if condition = @conditions[method]
110
+ if condition.respond_to?(:generate)
111
+ result = condition.generate(params, recall, options)
112
+ end
113
+ end
114
+ end
115
+
116
+ if result
117
+ @defaults.each do |key, value|
118
+ params.delete(key) if params[key] == value
119
+ end
120
+ end
121
+
122
+ result
123
+ end
124
+
125
+
126
+ def inspect #:nodoc:
127
+ "#<#{self.class.name} @app=#{@app.inspect} @conditions=#{@conditions.inspect} @defaults=#{@defaults.inspect} @name=#{@name.inspect}>"
128
+ end
129
+ end
130
+ end