utopia 0.12.6 → 1.0.0

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 (126) hide show
  1. checksums.yaml +4 -4
  2. data/.travis.yml +6 -2
  3. data/Gemfile +6 -0
  4. data/README.md +48 -14
  5. data/Rakefile +5 -0
  6. data/bin/utopia +132 -15
  7. data/lib/utopia.rb +13 -10
  8. data/lib/utopia/content.rb +140 -0
  9. data/lib/utopia/content/link.rb +124 -0
  10. data/lib/utopia/content/links.rb +228 -0
  11. data/lib/utopia/content/node.rb +387 -0
  12. data/lib/utopia/content/processor.rb +128 -0
  13. data/lib/utopia/content/tag.rb +102 -0
  14. data/lib/utopia/controller.rb +137 -0
  15. data/lib/utopia/controller/action.rb +112 -0
  16. data/lib/utopia/controller/base.rb +174 -0
  17. data/lib/utopia/{middleware/controller → controller}/variables.rb +36 -38
  18. data/lib/utopia/exception_handler.rb +79 -0
  19. data/lib/utopia/extensions/array.rb +2 -2
  20. data/lib/utopia/localization.rb +143 -0
  21. data/lib/utopia/mail_exceptions.rb +136 -0
  22. data/lib/utopia/middleware.rb +7 -22
  23. data/lib/utopia/path.rb +150 -60
  24. data/lib/utopia/redirector.rb +152 -0
  25. data/lib/utopia/{extensions/hash.rb → session.rb} +4 -6
  26. data/lib/utopia/session/encrypted_cookie.rb +46 -48
  27. data/lib/utopia/{middleware/directory_index.rb → session/lazy_hash.rb} +44 -27
  28. data/lib/utopia/static.rb +255 -0
  29. data/lib/utopia/tags/deferred.rb +12 -8
  30. data/lib/utopia/tags/environment.rb +18 -6
  31. data/lib/utopia/tags/node.rb +12 -8
  32. data/lib/utopia/tags/override.rb +12 -12
  33. data/lib/utopia/version.rb +1 -1
  34. data/setup/.bowerrc +3 -0
  35. data/{lib/utopia/setup → setup}/Gemfile +1 -1
  36. data/setup/Rakefile +4 -0
  37. data/{lib/utopia/setup → setup}/cache/head/readme.txt +0 -0
  38. data/{lib/utopia/setup → setup}/cache/meta/readme.txt +0 -0
  39. data/setup/config.ru +64 -0
  40. data/{lib/utopia/setup → setup}/lib/readme.txt +0 -0
  41. data/{lib/utopia/setup → setup}/pages/_heading.xnode +0 -0
  42. data/{lib/utopia/setup → setup}/pages/_page.xnode +1 -1
  43. data/{lib/utopia/setup → setup}/pages/_static/icon.png +0 -0
  44. data/setup/pages/_static/site.css +70 -0
  45. data/{lib/utopia/setup → setup}/pages/errors/exception.xnode +0 -0
  46. data/{lib/utopia/setup → setup}/pages/errors/file-not-found.xnode +0 -0
  47. data/{lib/utopia/setup → setup}/pages/links.yaml +0 -0
  48. data/setup/pages/welcome/index.xnode +17 -0
  49. data/{lib/utopia/setup → setup}/public/readme.txt +0 -0
  50. data/spec/utopia/content/link_spec.rb +108 -0
  51. data/spec/utopia/content/links/foo/index.xnode +0 -0
  52. data/spec/utopia/content/links/foo/links.yaml +2 -0
  53. data/spec/utopia/content/links/foo/test.de.xnode +0 -0
  54. data/spec/utopia/content/links/foo/test.en.xnode +0 -0
  55. data/spec/utopia/content/links/links.yaml +9 -0
  56. data/spec/utopia/content/links/welcome.xnode +0 -0
  57. data/spec/utopia/content/localized/five/index.en.xnode +0 -0
  58. data/spec/utopia/content/localized/four/index.en.xnode +0 -0
  59. data/spec/utopia/content/localized/four/index.zh.xnode +0 -0
  60. data/spec/utopia/content/localized/four/links.yaml +4 -0
  61. data/spec/utopia/content/localized/links.yaml +16 -0
  62. data/spec/utopia/content/localized/one.xnode +0 -0
  63. data/spec/utopia/content/localized/three/index.xnode +0 -0
  64. data/spec/utopia/content/localized/two.en.xnode +0 -0
  65. data/spec/utopia/content/localized/two.zh.xnode +0 -0
  66. data/spec/utopia/content/node/ordered/first.xnode +0 -0
  67. data/spec/utopia/content/node/ordered/index.xnode +0 -0
  68. data/spec/utopia/content/node/ordered/links.yaml +4 -0
  69. data/spec/utopia/content/node/ordered/second.xnode +0 -0
  70. data/spec/utopia/content/node/related/foo.en.xnode +0 -0
  71. data/spec/utopia/content/node/related/foo.ja.xnode +0 -0
  72. data/spec/utopia/content/node/related/links.yaml +4 -0
  73. data/spec/utopia/content/node_spec.rb +63 -0
  74. data/spec/utopia/{middleware/content_spec.rb → content/processor_spec.rb} +34 -23
  75. data/spec/utopia/content_spec.rb +87 -0
  76. data/spec/utopia/content_spec.ru +10 -0
  77. data/spec/utopia/{middleware/controller_spec.rb → controller_spec.rb} +61 -16
  78. data/spec/utopia/controller_spec.ru +4 -0
  79. data/spec/utopia/extensions_spec.rb +6 -17
  80. data/spec/utopia/localization_spec.rb +60 -0
  81. data/spec/utopia/localization_spec.ru +11 -0
  82. data/{lib/utopia/tags.rb → spec/utopia/middleware_spec.rb} +8 -14
  83. data/spec/utopia/{middleware/content_root → pages}/_heading.xnode +0 -0
  84. data/spec/utopia/pages/content/_show-value.xnode +1 -0
  85. data/spec/utopia/pages/content/test-partial.xnode +1 -0
  86. data/spec/utopia/pages/controller/controller.rb +28 -0
  87. data/spec/utopia/pages/controller/index.xnode +1 -0
  88. data/spec/utopia/pages/controller/nested/controller.rb +4 -0
  89. data/spec/utopia/{middleware/content_root → pages}/index.xnode +0 -0
  90. data/spec/utopia/pages/localized.de.txt +1 -0
  91. data/spec/utopia/pages/localized.en.txt +1 -0
  92. data/spec/utopia/pages/localized.jp.txt +1 -0
  93. data/spec/utopia/pages/node/index.xnode +1 -0
  94. data/spec/utopia/pages/test.txt +1 -0
  95. data/spec/utopia/path_spec.rb +109 -0
  96. data/spec/utopia/rack_spec.rb +2 -0
  97. data/spec/utopia/session_spec.rb +82 -0
  98. data/spec/utopia/session_spec.ru +20 -0
  99. data/spec/utopia/spec_helper.rb +16 -0
  100. data/{lib/utopia/extensions/string.rb → spec/utopia/static_spec.rb} +24 -15
  101. data/spec/utopia/static_spec.ru +4 -0
  102. data/utopia.gemspec +3 -3
  103. metadata +138 -54
  104. data/lib/utopia/extensions/regexp.rb +0 -33
  105. data/lib/utopia/link.rb +0 -288
  106. data/lib/utopia/middleware/all.rb +0 -33
  107. data/lib/utopia/middleware/content.rb +0 -157
  108. data/lib/utopia/middleware/content/node.rb +0 -386
  109. data/lib/utopia/middleware/content/processor.rb +0 -123
  110. data/lib/utopia/middleware/controller.rb +0 -130
  111. data/lib/utopia/middleware/controller/action.rb +0 -121
  112. data/lib/utopia/middleware/controller/base.rb +0 -184
  113. data/lib/utopia/middleware/exception_handler.rb +0 -80
  114. data/lib/utopia/middleware/localization.rb +0 -147
  115. data/lib/utopia/middleware/localization/name.rb +0 -69
  116. data/lib/utopia/middleware/mail_exceptions.rb +0 -138
  117. data/lib/utopia/middleware/redirector.rb +0 -146
  118. data/lib/utopia/middleware/requester.rb +0 -126
  119. data/lib/utopia/middleware/static.rb +0 -295
  120. data/lib/utopia/setup.rb +0 -60
  121. data/lib/utopia/setup/config.ru +0 -47
  122. data/lib/utopia/setup/pages/_static/background.png +0 -0
  123. data/lib/utopia/setup/pages/_static/site.css +0 -48
  124. data/lib/utopia/setup/pages/welcome/index.xnode +0 -7
  125. data/lib/utopia/tag.rb +0 -105
  126. data/lib/utopia/tags/all.rb +0 -34
@@ -0,0 +1,174 @@
1
+ # Copyright, 2014, by Samuel G. D. Williams. <http://www.codeotaku.com>
2
+ #
3
+ # Permission is hereby granted, free of charge, to any person obtaining a copy
4
+ # of this software and associated documentation files (the "Software"), to deal
5
+ # in the Software without restriction, including without limitation the rights
6
+ # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7
+ # copies of the Software, and to permit persons to whom the Software is
8
+ # furnished to do so, subject to the following conditions:
9
+ #
10
+ # The above copyright notice and this permission notice shall be included in
11
+ # all copies or substantial portions of the Software.
12
+ #
13
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14
+ # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15
+ # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16
+ # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17
+ # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18
+ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
19
+ # THE SOFTWARE.
20
+
21
+ require_relative '../http'
22
+
23
+ module Utopia
24
+ class Controller
25
+ class Base
26
+ def self.base_path
27
+ self.const_get(:BASE_PATH)
28
+ end
29
+
30
+ def self.uri_path
31
+ self.const_get(:URI_PATH)
32
+ end
33
+
34
+ def self.controller
35
+ self.const_get(:CONTROLLER)
36
+ end
37
+
38
+ class << self
39
+ def require_local(path)
40
+ require File.join(base_path, path)
41
+ end
42
+
43
+ def direct?(path)
44
+ path.dirname == uri_path
45
+ end
46
+
47
+ def actions
48
+ @actions ||= Action.new
49
+ end
50
+
51
+ def on(path, options = {}, &block)
52
+ if Symbol === path
53
+ path = ['**', path]
54
+ end
55
+
56
+ actions.define(Path.create(path).components, options, &block)
57
+ end
58
+
59
+ def lookup(path)
60
+ possible_actions = actions.select((path - uri_path).components)
61
+ end
62
+ end
63
+
64
+ # Given a path, look up all matched actions.
65
+ def lookup(path)
66
+ self.class.lookup(path)
67
+ end
68
+
69
+ # Given a request, call associated actions if at least one exists.
70
+ def passthrough(request, path)
71
+ actions = lookup(path)
72
+
73
+ if actions.size > 0
74
+ variables = request.controller
75
+ controller_clone = self.clone
76
+
77
+ variables << controller_clone
78
+
79
+ response = catch(:response) do
80
+ # By default give nothing - i.e. keep on processing:
81
+ actions.each do |action|
82
+ action.invoke!(controller_clone, request, path)
83
+ end and nil
84
+ end
85
+
86
+ if response
87
+ return controller_clone.respond_with(*response)
88
+ end
89
+ end
90
+
91
+ return nil
92
+ end
93
+
94
+ # Copy the instance variables from the previous controller to the next controller (usually only a few). This allows controllers to share effectively the same instance variables while still being separate classes/instances.
95
+ def copy_instance_variables(from)
96
+ from.instance_variables.each do |name|
97
+ instance_variable_set(name, from.instance_variable_get(name))
98
+ end
99
+ end
100
+
101
+ def call(env)
102
+ self.class.controller.app.call(env)
103
+ end
104
+
105
+ def respond!(*args)
106
+ throw :response, args
107
+ end
108
+
109
+ def ignore!
110
+ throw :response, nil
111
+ end
112
+
113
+ def redirect! (target, status = 302)
114
+ respond! :redirect => target.to_str, :status => status
115
+ end
116
+
117
+ def rewrite! location
118
+ throw :rewrite, location.to_str
119
+ end
120
+
121
+ def fail!(error = :bad_request)
122
+ respond! error
123
+ end
124
+
125
+ def success!(*args)
126
+ respond! :success, *args
127
+ end
128
+
129
+ def respond_with(*args)
130
+ return args[0] if args[0] == nil || Array === args[0]
131
+
132
+ status = 200
133
+ options = nil
134
+
135
+ if Numeric === args[0] || Symbol === args[0]
136
+ status = args[0]
137
+ options = args[1] || {}
138
+ else
139
+ options = args[0]
140
+ status = options[:status] || status
141
+ end
142
+
143
+ status = Utopia::HTTP::STATUS_CODES[status] || status
144
+ headers = options[:headers] || {}
145
+
146
+ if options[:type]
147
+ headers['Content-Type'] ||= options[:type]
148
+ end
149
+
150
+ if options[:redirect]
151
+ headers["Location"] = options[:redirect]
152
+ status = 302 if status < 300 || status >= 400
153
+ end
154
+
155
+ body = []
156
+ if options[:body]
157
+ body = options[:body]
158
+ elsif options[:content]
159
+ body = [options[:content]]
160
+ elsif status >= 300
161
+ body = [Utopia::HTTP::STATUS_DESCRIPTIONS[status] || "Status #{status}"]
162
+ end
163
+
164
+ return [status, headers, body]
165
+ end
166
+
167
+ # Return nil if this controller didn't do anything. Request will keep on processing. Return a valid rack response if the controller can do so.
168
+ def process!(request, path)
169
+ # puts "process! #{request} #{path}"
170
+ passthrough(request, path)
171
+ end
172
+ end
173
+ end
174
+ end
@@ -19,53 +19,51 @@
19
19
  # THE SOFTWARE.
20
20
 
21
21
  module Utopia
22
- module Middleware
23
- class Controller
24
- class Variables
25
- def initialize
26
- @controllers = []
27
- end
22
+ class Controller
23
+ class Variables
24
+ def initialize
25
+ @controllers = []
26
+ end
28
27
 
29
- def << controller
30
- top = @controllers.last
31
-
32
- @controllers << controller
33
-
34
- # This ensures that most variables will be at the top and controllers can naturally interactive with instance variables.
35
- controller.copy_instance_variables(top) if top
36
- end
28
+ def << controller
29
+ top = @controllers.last
30
+
31
+ @controllers << controller
32
+
33
+ # This ensures that most variables will be at the top and controllers can naturally interactive with instance variables.
34
+ controller.copy_instance_variables(top) if top
35
+ end
37
36
 
38
- def fetch(key)
39
- @controllers.reverse_each do |controller|
40
- if controller.instance_variables.include?(key)
41
- return controller.instance_variable_get(key)
42
- end
43
- end
44
-
45
- if block_given?
46
- yield key
47
- else
48
- raise KeyError.new(key)
37
+ def fetch(key)
38
+ @controllers.reverse_each do |controller|
39
+ if controller.instance_variables.include?(key)
40
+ return controller.instance_variable_get(key)
49
41
  end
50
42
  end
43
+
44
+ if block_given?
45
+ yield key
46
+ else
47
+ raise KeyError.new(key)
48
+ end
49
+ end
51
50
 
52
- def to_hash
53
- attributes = {}
51
+ def to_hash
52
+ attributes = {}
54
53
 
55
- @controllers.each do |controller|
56
- controller.instance_variables.each do |name|
57
- key = name[1..-1]
58
-
59
- attributes[key] = controller.instance_variable_get(name)
60
- end
54
+ @controllers.each do |controller|
55
+ controller.instance_variables.each do |name|
56
+ key = name[1..-1]
57
+
58
+ attributes[key] = controller.instance_variable_get(name)
61
59
  end
62
-
63
- return attributes
64
60
  end
65
61
 
66
- def [] key
67
- fetch("@#{key}".to_sym) { nil }
68
- end
62
+ return attributes
63
+ end
64
+
65
+ def [] key
66
+ fetch("@#{key}".to_sym) { nil }
69
67
  end
70
68
  end
71
69
  end
@@ -0,0 +1,79 @@
1
+ # Copyright, 2014, by Samuel G. D. Williams. <http://www.codeotaku.com>
2
+ #
3
+ # Permission is hereby granted, free of charge, to any person obtaining a copy
4
+ # of this software and associated documentation files (the "Software"), to deal
5
+ # in the Software without restriction, including without limitation the rights
6
+ # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7
+ # copies of the Software, and to permit persons to whom the Software is
8
+ # furnished to do so, subject to the following conditions:
9
+ #
10
+ # The above copyright notice and this permission notice shall be included in
11
+ # all copies or substantial portions of the Software.
12
+ #
13
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14
+ # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15
+ # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16
+ # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17
+ # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18
+ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
19
+ # THE SOFTWARE.
20
+
21
+ require_relative 'middleware'
22
+
23
+ require 'trenni/strings'
24
+
25
+ module Utopia
26
+ class ExceptionHandler
27
+ def initialize(app, location)
28
+ @app = app
29
+
30
+ @location = location
31
+ end
32
+
33
+ def fatal_error(env, exception)
34
+ body = StringIO.new
35
+
36
+ body.puts "<!DOCTYPE html><html><head><title>Fatal Error</title></head><body>"
37
+ body.puts "<h1>Fatal Error</h1>"
38
+ body.puts "<p>While requesting resource #{Trenni::Strings::to_html env['PATH_INFO']}, a fatal error occurred.</p>"
39
+ body.puts "<blockquote><strong>#{Trenni::Strings::to_html exception.class.name}</strong>: #{Trenni::Strings::to_html exception.to_s}</blockquote>"
40
+ body.puts "<p>There is nothing more we can do to fix the problem at this point.</p>"
41
+ body.puts "<p>We apologize for the inconvenience.</p>"
42
+ body.puts "</body></html>"
43
+ body.rewind
44
+
45
+ return [400, {"Content-Type" => "text/html"}, body]
46
+ end
47
+
48
+ def redirect(env, ex)
49
+ return @app.call(env.merge('PATH_INFO' => @location, 'REQUEST_METHOD' => 'GET'))
50
+ end
51
+
52
+ def call(env)
53
+ begin
54
+ return @app.call(env)
55
+ rescue Exception => exception
56
+ # An error has occurred, log it:
57
+ log = ::Logger.new(env['rack.errors'] || $stderr)
58
+
59
+ log.error "Exception #{exception.to_s.dump}!"
60
+
61
+ ex.backtrace.each do |bt|
62
+ log.error bt
63
+ end
64
+
65
+ # If the error occurred while accessing the error handler, we finish with a fatal error:
66
+ if env['PATH_INFO'] == @location
67
+ return fatal_error(env, ex)
68
+ else
69
+ # If redirection fails, we also finish with a fatal error:
70
+ begin
71
+ return redirect(env, ex)
72
+ rescue
73
+ return fatal_error(env, ex)
74
+ end
75
+ end
76
+ end
77
+ end
78
+ end
79
+ end
@@ -19,8 +19,8 @@
19
19
  # THE SOFTWARE.
20
20
 
21
21
  class Array
22
- def split_at(&block)
23
- if middle = index(&block)
22
+ def split_at(*args, &block)
23
+ if middle = index(*args, &block)
24
24
  [self[0...middle], self[middle], self[middle+1..-1]]
25
25
  else
26
26
  [[], nil, []]
@@ -0,0 +1,143 @@
1
+ # Copyright, 2012, by Samuel G. D. Williams. <http://www.codeotaku.com>
2
+ #
3
+ # Permission is hereby granted, free of charge, to any person obtaining a copy
4
+ # of this software and associated documentation files (the "Software"), to deal
5
+ # in the Software without restriction, including without limitation the rights
6
+ # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7
+ # copies of the Software, and to permit persons to whom the Software is
8
+ # furnished to do so, subject to the following conditions:
9
+ #
10
+ # The above copyright notice and this permission notice shall be included in
11
+ # all copies or substantial portions of the Software.
12
+ #
13
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14
+ # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15
+ # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16
+ # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17
+ # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18
+ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
19
+ # THE SOFTWARE.
20
+
21
+ require_relative 'middleware'
22
+
23
+ module Rack
24
+ class Request
25
+ def current_locale
26
+ env[Utopia::Localization::CURRENT_LOCALE_KEY]
27
+ end
28
+
29
+ def default_locale
30
+ localization.default_locale
31
+ end
32
+
33
+ def all_locales
34
+ localization.all_locales
35
+ end
36
+
37
+ def localization
38
+ env[Utopia::Localization::LOCALIZATION_KEY]
39
+ end
40
+ end
41
+ end
42
+
43
+ module Utopia
44
+ # If you request a URL which has localized content, a localized redirect would be returned based on the content requested.
45
+ class Localization
46
+ RESOURCE_NOT_FOUND = [400, {}, []].freeze
47
+
48
+ HTTP_ACCEPT_LANGUAGE = 'HTTP_ACCEPT_LANGUAGE'.freeze
49
+ LOCALIZATION_KEY = 'utopia.localization'.freeze
50
+ CURRENT_LOCALE_KEY = 'utopia.localization.current_locale'.freeze
51
+
52
+ def initialize(app, options = {})
53
+ @app = app
54
+
55
+ @default_locale = options[:default_locale] || "en"
56
+ @all_locales = options[:locales] || ["en"]
57
+
58
+ @nonlocalized = options.fetch(:nonlocalized, [])
59
+ end
60
+
61
+ attr :all_locales
62
+ attr :default_locale
63
+
64
+ def preferred_locales(env)
65
+ request_preferred_locales(env) | browser_preferred_locales(env) | [@default_locale, nil]
66
+ end
67
+
68
+ def request_preferred_locales(env)
69
+ path = Path[env['PATH_INFO']]
70
+
71
+ if all_locales.include? path.first
72
+ request_locale = path.first
73
+
74
+ # Remove the localization prefix.
75
+ path.delete_at(0)
76
+ env['PATH_INFO'] = path.to_s
77
+
78
+ return [request_locale]
79
+ else
80
+ return []
81
+ end
82
+ end
83
+
84
+ def browser_preferred_locales(env)
85
+ accept_languages = env[HTTP_ACCEPT_LANGUAGE]
86
+
87
+ # No user prefered languages:
88
+ return [] unless accept_languages
89
+
90
+ languages = accept_languages.split(',').map { |language|
91
+ language.split(';q=').tap{|x| x[1] = (x[1] || 1.0).to_f}
92
+ }.sort{|a, b| b[1] <=> a[1]}.collect(&:first)
93
+
94
+ # Returns languages based on the order of the first argument
95
+ return languages & @all_locales
96
+ end
97
+
98
+ def nonlocalized?(env)
99
+ path_info = env['PATH_INFO']
100
+
101
+ @nonlocalized.any? { |pattern| path_info[pattern] != nil }
102
+ end
103
+
104
+ # Set the Vary: header on the response to indicate that this response should include the header in the cache key.
105
+ def vary(env, response)
106
+ headers = response[1]
107
+
108
+ # This response was based on the Accept-Language header:
109
+ if headers['Vary']
110
+ headers['Vary'] += ',Accept-Language'
111
+ else
112
+ headers['Vary'] = 'Accept-Language'
113
+ end
114
+
115
+ # Althought this header is generally not supported, we supply it anyway as it is useful for debugging:
116
+ if locale = env[CURRENT_LOCALE_KEY]
117
+ # Set the Content-Location to point to the localized URI as requested:
118
+ headers['Content-Location'] = "/#{locale}" + env['PATH_INFO']
119
+ end
120
+
121
+ return response
122
+ end
123
+
124
+ def call(env)
125
+ return @app.call(env) if nonlocalized?(env)
126
+
127
+ env[LOCALIZATION_KEY] = self
128
+
129
+ response = nil
130
+
131
+ # We have a non-localized request, but there might be a localized resource. We return the best localization possible:
132
+ preferred_locales(env).each do |locale|
133
+ env[CURRENT_LOCALE_KEY] = locale
134
+
135
+ response = @app.call(env)
136
+
137
+ break unless response[0] >= 400
138
+ end
139
+
140
+ return vary(env, response)
141
+ end
142
+ end
143
+ end