lgierth-rack-mount 0.6.13

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