utopia 1.1.4 → 1.2.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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 0ba1d6de864b4c631f7eea25e878c6612ea5c83e
4
- data.tar.gz: 8fce1f1e3820075e0c5b3bf20cbf32eb9e1cebcc
3
+ metadata.gz: 31ace1eb6c636da91d00f8eee84b9dcaf839f817
4
+ data.tar.gz: 27d8124f0df0d66189d9230cce119db1a39901cd
5
5
  SHA512:
6
- metadata.gz: 353a709c2b9b2eefeb612f37e57e23e73a4991458cc623a8e611842e8da329ed225f919ae16560734ba4da3f0906c6a296acb0c5ec7dc1ecff6d376de5dfcc93
7
- data.tar.gz: 898b1484f1369e06f289b91441d9d2d2152c1931e9a386ca7a0614fde2a8d4269211d1557ee2d6eaaf97b137e1a3b90d5b55bdf9a18dd7ca3ad5483b267a016b
6
+ metadata.gz: 1ccc85d249b116d20fcba8403244b8d466aecfe527cf0a13aa79b19ee78e162c6acd9e458a40bf5064dae17b2108c0b4bd851ca2e4fc06a7e84990db148abeac
7
+ data.tar.gz: 5e95784314d4c28253714caac80b9efec859f988bf908784da15d65c2d460623ee2607505a93b931b392558b7bbfe41c328814cd1631a6979a024016931dd062
@@ -23,6 +23,7 @@ require 'trenni/builder'
23
23
 
24
24
  require_relative '../content'
25
25
  require_relative '../path'
26
+ require_relative '../locale'
26
27
 
27
28
  module Utopia
28
29
  class Content
@@ -35,16 +36,16 @@ module Utopia
35
36
 
36
37
  case @kind
37
38
  when :file
38
- @name, @variant = path.last.split('.', 2)
39
+ @name, @locale = path.last.split('.', 2)
39
40
  @path = path
40
41
  when :directory
41
42
  # raise ArgumentError unless path.last.start_with? INDEX
42
43
 
43
44
  @name = path.dirname.last
44
- @variant = path.last.split('.', 2)[1]
45
+ @locale = path.last.split('.', 2)[1]
45
46
  @path = path
46
47
  when :virtual
47
- @name, @variant = path.to_s.split('.', 2)
48
+ @name, @locale = path.to_s.split('.', 2)
48
49
  @path = @info[:path] ? Path.create(@info[:path]) : nil
49
50
  else
50
51
  raise ArgumentError.new("Unknown link kind #{@kind} with path #{path}")
@@ -67,7 +68,7 @@ module Utopia
67
68
  attr :name
68
69
  attr :path
69
70
  attr :info
70
- attr :variant
71
+ attr :locale
71
72
 
72
73
  def href?
73
74
  !!href
@@ -26,10 +26,10 @@ module Utopia
26
26
 
27
27
  # Links are essentially a static list of information relating to the structure of the content. They are formed from the `links.yaml` file and the actual files on disk.
28
28
  class Links
29
- def self.for(root, path, variant = nil)
29
+ def self.for(root, path, locale = nil)
30
30
  links = self.new(root, path.dirname)
31
31
 
32
- links.lookup(path.last, variant)
32
+ links.lookup(path.last, locale)
33
33
  end
34
34
 
35
35
  DEFAULT_INDEX_OPTIONS = {
@@ -56,18 +56,18 @@ module Utopia
56
56
  ordered.select!{|link| link.name[options[:name]]}
57
57
  end
58
58
 
59
- if variant = options[:variant]
60
- variants = {}
59
+ if locale = options[:locale]
60
+ locales = {}
61
61
 
62
62
  ordered.each do |link|
63
- if link.variant == variant
64
- variants[link.name] = link
65
- elsif link.variant == nil
66
- variants[link.name] ||= link
63
+ if link.locale == locale
64
+ locales[link.name] = link
65
+ elsif link.locale == nil
66
+ locales[link.name] ||= link
67
67
  end
68
68
  end
69
69
 
70
- ordered = variants.values
70
+ ordered = locales.values
71
71
  end
72
72
 
73
73
  # Sort:
@@ -115,18 +115,18 @@ module Utopia
115
115
  attr :ordered
116
116
  attr :named
117
117
 
118
- def each(variant)
119
- return to_enum(:each, variant) unless block_given?
118
+ def each(locale)
119
+ return to_enum(:each, locale) unless block_given?
120
120
 
121
121
  ordered.each do |links|
122
- yield links.find{|link| link.variant == variant}
122
+ yield links.find{|link| link.locale == locale}
123
123
  end
124
124
  end
125
125
 
126
- def lookup(name, variant = nil)
127
- # This allows generic links to serve any variant requested.
126
+ def lookup(name, locale = nil)
127
+ # This allows generic links to serve any locale requested.
128
128
  if links = @named[name]
129
- links.find{|link| link.variant == variant} || links.find{|link| link.variant == nil}
129
+ links.find{|link| link.locale == locale} || links.find{|link| link.locale == nil}
130
130
  end
131
131
  end
132
132
 
@@ -145,7 +145,7 @@ module Utopia
145
145
  links_path = File.join(path, LINKS_YAML)
146
146
 
147
147
  hash = if File.exist?(links_path)
148
- YAML::load(File.read(links_path)) || {}
148
+ YAML::load_file(links_path) || {}
149
149
  else
150
150
  {}
151
151
  end
@@ -171,9 +171,10 @@ module Utopia
171
171
  directory_link = Link.new(:directory, @top + [name, index_name], index_metadata)
172
172
 
173
173
  # Merge metadata from foo.en into foo/index.en
174
- if directory_link.variant
175
- if variant_metadata = metadata.delete(directory_link.name + '.' + directory_link.variant)
176
- directory_link.info.update(variant_metadata)
174
+ if directory_link.locale
175
+ localized_key = "#{directory_link.name}.#{directory_link.locale}"
176
+ if localized_metadata = metadata.delete(localized_key)
177
+ directory_link.info.update(localized_metadata)
177
178
  end
178
179
  end
179
180
 
@@ -18,7 +18,6 @@
18
18
  # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
19
19
  # THE SOFTWARE.
20
20
 
21
- require_relative 'processor'
22
21
  require_relative 'links'
23
22
 
24
23
  module Utopia
@@ -152,6 +151,10 @@ module Utopia
152
151
  return current.attributes
153
152
  end
154
153
 
154
+ def localization
155
+ @localization ||= Utopia::Localization[request]
156
+ end
157
+
155
158
  def current
156
159
  @begin_tags[-1]
157
160
  end
@@ -45,7 +45,6 @@ module Utopia
45
45
 
46
46
  class Controller
47
47
  CONTROLLER_RB = 'controller.rb'.freeze
48
- PATH_INFO_KEY = 'PATH_INFO'.freeze
49
48
 
50
49
  def initialize(app, **options)
51
50
  @app = app
@@ -119,7 +118,7 @@ module Utopia
119
118
  end
120
119
 
121
120
  # The controllers may have rewriten the path so we update the path info:
122
- request.env[PATH_INFO_KEY] = controller_path.to_s
121
+ request.env[Rack::PATH_INFO] = controller_path.to_s
123
122
 
124
123
  # No controller gave a useful result:
125
124
  return nil
@@ -22,8 +22,6 @@ require_relative '../http'
22
22
 
23
23
  module Utopia
24
24
  class Controller
25
- EMPTY_BODY = [].freeze
26
-
27
25
  class Base
28
26
  def self.base_path
29
27
  self.const_get(:BASE_PATH)
@@ -75,13 +73,9 @@ module Utopia
75
73
  end
76
74
 
77
75
  def catch_response
78
- response = catch(:response) do
76
+ catch(:response) do
79
77
  yield and nil
80
78
  end
81
-
82
- if response
83
- return self.respond_with(*response)
84
- end
85
79
  end
86
80
 
87
81
  # Given a request, call associated actions if at least one exists.
@@ -106,69 +100,65 @@ module Utopia
106
100
  end
107
101
  end
108
102
 
103
+ # Call into the next app as defined by rack.
109
104
  def call(env)
110
105
  self.class.controller.app.call(env)
111
106
  end
112
-
113
- def respond!(*args)
114
- throw :response, args
107
+
108
+ # This will cause the middleware to generate a response.
109
+ def respond!(response)
110
+ throw :response, response
115
111
  end
116
112
 
113
+ # This will cause the controller middleware to pass on the request.
117
114
  def ignore!
118
115
  throw :response, nil
119
116
  end
120
117
 
118
+ # Request relative redirect. Respond with a redirect to the given target.
121
119
  def redirect! (target, status = 302)
122
- respond! :redirect => target, :status => status
120
+ status = HTTP::Status.new(status, 300...400)
121
+ location = target.to_s
122
+
123
+ respond! [status.to_i, {HTTP::LOCATION => location}, [status.to_s]]
123
124
  end
124
-
125
- def fail!(error = :bad_request)
126
- respond! error
125
+
126
+ # Controller relative redirect.
127
+ def goto!(target, status = 302)
128
+ redirect! self.class.uri_path + target
127
129
  end
128
-
129
- def success!(*args)
130
- respond! :success, *args
130
+
131
+ # Respond with an error which indiciates some kind of failure.
132
+ def fail!(error = 400, message = nil)
133
+ status = HTTP::Status.new(error, 400...600)
134
+
135
+ message ||= status.to_s
136
+ respond! [status.to_i, {}, [message]]
131
137
  end
132
138
 
133
- def respond_with(*args)
134
- return args[0] if args[0] == nil || Array === args[0]
135
-
136
- status = 200
137
- options = nil
138
-
139
- if Numeric === args[0] || Symbol === args[0]
140
- status = args[0]
141
- options = args[1] || {}
142
- else
143
- options = args[0]
144
- status = options[:status] || status
145
- end
146
-
147
- status = HTTP::STATUS_CODES[status] || status
148
- headers = options[:headers] || {}
149
-
150
- if type = options[:type]
151
- headers[HTTP::CONTENT_TYPE] ||= type
152
- end
153
-
154
- if redirect = options[:redirect]
155
- headers[HTTP::LOCATION] = redirect.to_s
156
- status = 302 if status < 300 || status >= 400
139
+ def succeed!(status: 200, headers: {}, **options)
140
+ status = HTTP::Status.new(status, 200...300)
141
+
142
+ if options[:type]
143
+ headers[Rack::CONTENT_TYPE] = options[:type].to_s
157
144
  end
158
-
159
- if options[:body]
160
- body = options[:body]
161
- elsif options[:content]
162
- body = [options[:content]]
163
- elsif status >= 300
164
- body = [HTTP::STATUS_DESCRIPTIONS[status] || "Status #{status}"]
165
- else
166
- body = EMPTY_BODY
145
+
146
+ body = body_for(status, headers, options)
147
+ respond! [status.to_i, headers, body || []]
148
+ end
149
+
150
+ # Generate the body for the given status, headers and options.
151
+ def body_for(status, headers, options)
152
+ if body = options[:body]
153
+ return body
154
+ elsif content = options[:content]
155
+ return [content]
167
156
  end
168
-
169
- return [status, headers, body]
170
157
  end
171
158
 
159
+ # Legacy method name:
160
+ alias success! succeed!
161
+
172
162
  # 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.
173
163
  def process!(request, path)
174
164
  passthrough(request, path)
@@ -35,7 +35,7 @@ module Utopia
35
35
 
36
36
  body.puts "<!DOCTYPE html><html><head><title>Fatal Error</title></head><body>"
37
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>"
38
+ body.puts "<p>While requesting resource #{Trenni::Strings::to_html env[Rack::PATH_INFO]}, a fatal error occurred.</p>"
39
39
  body.puts "<blockquote><strong>#{Trenni::Strings::to_html exception.class.name}</strong>: #{Trenni::Strings::to_html exception.to_s}</blockquote>"
40
40
  body.puts "<p>There is nothing more we can do to fix the problem at this point.</p>"
41
41
  body.puts "<p>We apologize for the inconvenience.</p>"
@@ -46,7 +46,7 @@ module Utopia
46
46
  end
47
47
 
48
48
  def redirect(env, exception)
49
- response = @app.call(env.merge('PATH_INFO' => @location, 'REQUEST_METHOD' => 'GET'))
49
+ response = @app.call(env.merge(Rack::PATH_INFO => @location, Rack::REQUEST_METHOD => Rack::GET))
50
50
 
51
51
  return [500, response[1], response[2]]
52
52
  end
@@ -65,7 +65,7 @@ module Utopia
65
65
  end
66
66
 
67
67
  # If the error occurred while accessing the error handler, we finish with a fatal error:
68
- if env['PATH_INFO'] == @location
68
+ if env[Rack::PATH_INFO] == @location
69
69
  return fatal_error(env, exception)
70
70
  else
71
71
  # If redirection fails, we also finish with a fatal error:
@@ -21,9 +21,9 @@
21
21
  require 'rack'
22
22
 
23
23
  module Rack
24
- unless defined? EXPIRES
25
- EXPIRES = 'Expires'.freeze
26
- end
24
+ # Compatibility with older versions of rack:
25
+ EXPIRES = 'Expires'.freeze unless defined? EXPIRES
26
+ HTTP_HOST = 'HTTP_HOST'.freeze unless defined? HTTP_HOST
27
27
 
28
28
  class Response
29
29
  # Specifies that the content shouldn't be cached. Overrides `cache!` if already called.
@@ -33,16 +33,24 @@ module Rack
33
33
  end
34
34
 
35
35
  # Specify that the content should be cached.
36
- def cache!(duration = 3600)
36
+ def cache!(duration = 3600, access = "public")
37
37
  unless headers[CACHE_CONTROL] =~ /no-cache/
38
- headers[CACHE_CONTROL] = "public, max-age=#{duration}"
38
+ headers[CACHE_CONTROL] = "#{access}, max-age=#{duration}"
39
39
  headers[EXPIRES] = (Time.now + duration).httpdate
40
40
  end
41
41
  end
42
42
 
43
43
  # Specify the content type of the response data.
44
44
  def content_type!(value)
45
- headers[CONTENT_TYPE] = value.to_s
45
+ self.content_type = value
46
+ end
47
+
48
+ def content_type= value
49
+ headers[CONTENT_TYPE] = value
50
+ end
51
+
52
+ def content_type
53
+ headers[CONTENT_TYPE]
46
54
  end
47
55
  end
48
56
  end
data/lib/utopia/http.rb CHANGED
@@ -35,6 +35,7 @@ module Utopia
35
35
  :unauthorized => 401,
36
36
  :forbidden => 403,
37
37
  :not_found => 404,
38
+ :not_allowed => 405,
38
39
  :unsupported_method => 405,
39
40
  :gone => 410,
40
41
  :teapot => 418,
@@ -42,13 +43,34 @@ module Utopia
42
43
  :unimplemented => 501,
43
44
  :unavailable => 503
44
45
  }
45
-
46
+
47
+ # For a more detailed description see https://en.wikipedia.org/wiki/List_of_HTTP_status_codes
46
48
  STATUS_DESCRIPTIONS = {
49
+ 200 => 'OK'.freeze,
50
+ 201 => 'Created'.freeze,
51
+ 202 => 'Accepted'.freeze,
52
+ 203 => 'Non-Authoritive Information'.freeze,
53
+ 204 => 'No Content'.freeze,
54
+ 205 => 'Reset Content'.freeze,
55
+ 206 => 'Partial Content'.freeze,
56
+ 300 => 'Multiple Choices'.freeze,
57
+ 301 => 'Moved Permanently'.freeze,
58
+ 302 => 'Found'.freeze,
59
+ 303 => 'See Other'.freeze,
60
+ 304 => 'Not Modified'.freeze,
61
+ 305 => 'Use Proxy'.freeze,
62
+ 307 => 'Temporary Redirect'.freeze,
63
+ 308 => 'Permanent Redirect'.freeze,
47
64
  400 => 'Bad Request'.freeze,
48
65
  401 => 'Permission Denied'.freeze,
66
+ 402 => 'Payment Required'.freeze,
49
67
  403 => 'Access Forbidden'.freeze,
50
68
  404 => 'Resource Not Found'.freeze,
51
69
  405 => 'Unsupported Method'.freeze,
70
+ 406 => 'Not Acceptable'.freeze,
71
+ 408 => 'Request Timeout'.freeze,
72
+ 409 => 'Request Conflict'.freeze,
73
+ 410 => 'Resource Removed'.freeze,
52
74
  416 => 'Byte range unsatisfiable'.freeze,
53
75
  500 => 'Internal Server Error'.freeze,
54
76
  501 => 'Not Implemented'.freeze,
@@ -57,5 +79,32 @@ module Utopia
57
79
 
58
80
  CONTENT_TYPE = 'Content-Type'.freeze
59
81
  LOCATION = 'Location'.freeze
82
+
83
+ class Status
84
+ def initialize(code, valid_range = 100...600)
85
+ if code.is_a? Symbol
86
+ code = STATUS_CODES[code]
87
+ end
88
+
89
+ unless 100...600
90
+ raise ArgumentError.new("Status must be in range #{valid_range}, was given #{code}!")
91
+ end
92
+
93
+ @code = code
94
+ end
95
+
96
+ def to_i
97
+ @code
98
+ end
99
+
100
+ def to_s
101
+ STATUS_DESCRIPTIONS[@code] || @code.to_s
102
+ end
103
+
104
+ # Allow to be used for rack body:
105
+ def each
106
+ yield to_s
107
+ end
108
+ end
60
109
  end
61
110
  end
@@ -0,0 +1,43 @@
1
+ # Copyright, 2015, 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
+ module Utopia
22
+ class Locale < Struct.new(:language, :country, :variant)
23
+ def to_s
24
+ to_a.compact.join('-')
25
+ end
26
+
27
+ def self.dump(instance)
28
+ if instance
29
+ instance.to_s
30
+ end
31
+ end
32
+
33
+ def self.load(instance)
34
+ if instance.is_a? String
35
+ self.new(*instance.split('-', 3))
36
+ elsif instance.is_a? Array
37
+ return self.new(*instance)
38
+ elsif instance.is_a? self
39
+ return instance
40
+ end
41
+ end
42
+ end
43
+ end
@@ -20,64 +20,125 @@
20
20
 
21
21
  require_relative 'middleware'
22
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
23
+ module Utopia
24
+ # If you request a URL which has localized content, a localized redirect would be returned based on the content requested.
25
+ class Localization
26
+ # A wrapper to provide easy access to locale related data in the request.
27
+ class RequestWrapper
28
+ def initialize(request)
29
+ if request.is_a? Rack::Request
30
+ @env = request.env
31
+ else
32
+ @env = request
33
+ end
34
+ end
35
+
36
+ def localization
37
+ @env[LOCALIZATION_KEY]
38
+ end
39
+
40
+ # Returns the current locale or nil if not localized.
41
+ def current_locale
42
+ @env[CURRENT_LOCALE_KEY]
43
+ end
44
+
45
+ # Returns the default locale or nil if not localized.
46
+ def default_locale
47
+ localization && localization.default_locale
48
+ end
49
+
50
+ # Returns an empty array if not localized.
51
+ def all_locales
52
+ localization && localization.all_locales || []
53
+ end
31
54
  end
32
55
 
33
- def all_locales
34
- localization.all_locales
56
+ def self.[] request
57
+ RequestWrapper.new(request)
35
58
  end
36
59
 
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
60
  RESOURCE_NOT_FOUND = [400, {}, []].freeze
47
61
 
48
62
  HTTP_ACCEPT_LANGUAGE = 'HTTP_ACCEPT_LANGUAGE'.freeze
49
63
  LOCALIZATION_KEY = 'utopia.localization'.freeze
50
64
  CURRENT_LOCALE_KEY = 'utopia.localization.current_locale'.freeze
51
65
 
66
+ DEFAULT_LOCALE = 'en'
67
+
52
68
  def initialize(app, **options)
53
69
  @app = app
54
-
55
- @default_locale = options[:default_locale] || "en"
56
- @all_locales = options[:locales] || ["en"]
70
+
71
+ @all_locales = options[:locales]
72
+
73
+ # Locales here are represented as an array of strings, e.g. ['en', 'ja', 'cn', 'de'].
74
+ unless @default_locales = options[:default_locales]
75
+ @default_locales = @all_locales + [nil]
76
+ end
77
+
78
+ if @default_locale = options[:default_locale]
79
+ @default_locales.unshift(default_locale)
80
+ else
81
+ @default_locale = @default_locales.first
82
+ end
83
+
84
+ @hosts = options[:hosts] || {}
57
85
 
58
86
  @nonlocalized = options.fetch(:nonlocalized, [])
87
+
88
+ # puts "All:#{@all_locales.inspect} defaults:#{@default_locales.inspect} default:#{default_locale}"
59
89
  end
60
90
 
61
91
  attr :all_locales
62
92
  attr :default_locale
63
93
 
64
94
  def preferred_locales(env)
65
- request_preferred_locales(env) | browser_preferred_locales(env) | [@default_locale, nil]
95
+ return to_enum(:preferred_locales, env) unless block_given?
96
+
97
+ # Keep track of what locales have been tried:
98
+ locales = Set.new
99
+
100
+ host_preferred_locales(env).each do |locale|
101
+ yield env.merge(CURRENT_LOCALE_KEY => locale) if locales.add? locale
102
+ end
103
+
104
+ request_preferred_locale(env) do |locale, path|
105
+ # We have extracted a locale from the path, so from this point on we should use the updated path:
106
+ env = env.merge(Rack::PATH_INFO => path.to_s)
107
+
108
+ yield env.merge(CURRENT_LOCALE_KEY => locale) if locales.add? locale
109
+ end
110
+
111
+ browser_preferred_locales(env).each do |locale|
112
+ yield env.merge(CURRENT_LOCALE_KEY => locale) if locales.add? locale
113
+ end
114
+
115
+ @default_locales.each do |locale|
116
+ yield env.merge(CURRENT_LOCALE_KEY => locale) if locales.add? locale
117
+ end
118
+ end
119
+
120
+ def host_preferred_locales(env)
121
+ http_host = env[Rack::HTTP_HOST]
122
+
123
+ locales = []
124
+
125
+ # Get a list of all hosts which match the incoming htt_host:
126
+ matching_hosts = @hosts.select{|host_pattern, locale| http_host =~ host_pattern}
127
+
128
+ # Extract all the valid locales:
129
+ matching_hosts.flat_map{|host_pattern, locale| locale}
66
130
  end
67
131
 
68
- def request_preferred_locales(env)
69
- path = Path[env['PATH_INFO']]
132
+ def request_preferred_locale(env)
133
+ path = Path[env[Rack::PATH_INFO]]
70
134
 
71
- if all_locales.include? path.first
135
+ if @all_locales.include? path.first
72
136
  request_locale = path.first
73
137
 
74
- # Remove the localization prefix.
138
+ # Remove the localization prefix:
75
139
  path.delete_at(0)
76
- env['PATH_INFO'] = path.to_s
77
140
 
78
- return [request_locale]
79
- else
80
- return []
141
+ yield request_locale, path
81
142
  end
82
143
  end
83
144
 
@@ -86,17 +147,17 @@ module Utopia
86
147
 
87
148
  # No user prefered languages:
88
149
  return [] unless accept_languages
89
-
150
+
90
151
  languages = accept_languages.split(',').map { |language|
91
152
  language.split(';q=').tap{|x| x[1] = (x[1] || 1.0).to_f}
92
153
  }.sort{|a, b| b[1] <=> a[1]}.collect(&:first)
93
154
 
94
- # Returns languages based on the order of the first argument
155
+ # Returns available languages based on the order of the first argument:
95
156
  return languages & @all_locales
96
157
  end
97
158
 
98
159
  def nonlocalized?(env)
99
- path_info = env['PATH_INFO']
160
+ path_info = env[Rack::PATH_INFO]
100
161
 
101
162
  @nonlocalized.any? { |pattern| path_info[pattern] != nil }
102
163
  end
@@ -115,13 +176,14 @@ module Utopia
115
176
  # Althought this header is generally not supported, we supply it anyway as it is useful for debugging:
116
177
  if locale = env[CURRENT_LOCALE_KEY]
117
178
  # Set the Content-Location to point to the localized URI as requested:
118
- headers['Content-Location'] = "/#{locale}" + env['PATH_INFO']
179
+ headers['Content-Location'] = "/#{locale}" + env[Rack::PATH_INFO]
119
180
  end
120
181
 
121
182
  return response
122
183
  end
123
184
 
124
185
  def call(env)
186
+ # Pass the request through with no localization if it is a nonlocalized path:
125
187
  return @app.call(env) if nonlocalized?(env)
126
188
 
127
189
  env[LOCALIZATION_KEY] = self
@@ -129,8 +191,8 @@ module Utopia
129
191
  response = nil
130
192
 
131
193
  # 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
194
+ preferred_locales(env) do |env|
195
+ # puts "Trying locale: #{env[CURRENT_LOCALE_KEY]}: #{env[Rack::PATH_INFO]}..."
134
196
 
135
197
  response = @app.call(env)
136
198
 
data/lib/utopia/path.rb CHANGED
@@ -52,7 +52,7 @@ module Utopia
52
52
  @parts ||= @name.split('.')
53
53
  end
54
54
 
55
- def variant
55
+ def locale
56
56
  parts.last if parts.size > 1
57
57
  end
58
58
 
@@ -322,10 +322,10 @@ module Utopia
322
322
  def == other
323
323
  return false unless other
324
324
 
325
- if other.is_a? String
326
- self.to_s == other
327
- else
328
- self.to_a == other.to_a
325
+ case other
326
+ when String then self.to_s == other
327
+ when Array then self.to_a == other
328
+ else other.is_a?(self.class) && @components == other.components
329
329
  end
330
330
  end
331
331
 
@@ -117,7 +117,7 @@ module Utopia
117
117
  end
118
118
 
119
119
  def call(env)
120
- base_path = env['PATH_INFO']
120
+ base_path = env[Rack::PATH_INFO]
121
121
 
122
122
  if uri = @strings[base_path]
123
123
  return redirect(@strings[base_path], base_path)
@@ -134,11 +134,11 @@ module Utopia
134
134
  response = @app.call(env)
135
135
 
136
136
  if @errors && response[0] >= 400 && uri = @errors[response[0]]
137
- error_request = env.merge("PATH_INFO" => uri, "REQUEST_METHOD" => "GET")
137
+ error_request = env.merge(Rack::PATH_INFO => uri, Rack::REQUEST_METHOD => Rack::GET)
138
138
  error_response = @app.call(error_request)
139
139
 
140
140
  if error_response[0] >= 400
141
- raise FailedRequestError.new(env['PATH_INFO'], response[0], uri, error_response[0])
141
+ raise FailedRequestError.new(env[Rack::PATH_INFO], response[0], uri, error_response[0])
142
142
  else
143
143
  # Feed the error code back with the error document
144
144
  error_response[0] = response[0]
@@ -20,6 +20,6 @@
20
20
 
21
21
  module Utopia
22
22
  module Session
23
- RACK_SESSION = "rack.session"
23
+ RACK_SESSION = "rack.session".freeze
24
24
  end
25
25
  end
data/lib/utopia/static.rb CHANGED
@@ -221,7 +221,7 @@ module Utopia
221
221
  attr :extensions
222
222
 
223
223
  def call(env)
224
- path_info = env['PATH_INFO']
224
+ path_info = env[Rack::PATH_INFO]
225
225
  extension = File.extname(path_info)
226
226
 
227
227
  if @extensions.key? extension.downcase
@@ -19,5 +19,5 @@
19
19
  # THE SOFTWARE.
20
20
 
21
21
  module Utopia
22
- VERSION = "1.1.4"
22
+ VERSION = "1.2.0"
23
23
  end
@@ -1,7 +1,7 @@
1
1
  <!DOCTYPE html>
2
2
  <html>
3
3
  <head>
4
- <?r response.content_type! "text/html; charset=utf-8" ?>
4
+ <?r response.content_type = "text/html; charset=utf-8" ?>
5
5
  <?r response.cache! ?>
6
6
 
7
7
  <?r if title = (attributes["title"] || @title) ?>
@@ -60,7 +60,7 @@ module Utopia::Content::LinksSpec
60
60
  links = Utopia::Content::Links.index(root, Utopia::Path.create("/foo"))
61
61
  expect(links.size).to be == 2
62
62
 
63
- links = Utopia::Content::Links.index(root, Utopia::Path.create("/foo"), variant: 'en')
63
+ links = Utopia::Content::Links.index(root, Utopia::Path.create("/foo"), locale: 'en')
64
64
  expect(links.size).to be == 1
65
65
  end
66
66
 
@@ -68,7 +68,7 @@ module Utopia::Content::LinksSpec
68
68
  root = File.expand_path("localized", __dir__)
69
69
 
70
70
  # Select both test links
71
- links = Utopia::Content::Links.index(root, Utopia::Path.create("/"), variant: 'en')
71
+ links = Utopia::Content::Links.index(root, Utopia::Path.create("/"), locale: 'en')
72
72
 
73
73
  expect(links.collect(&:title)).to be == ['One', 'Two', 'Three', 'Four', 'Five']
74
74
  end
@@ -77,7 +77,7 @@ module Utopia::Content::LinksSpec
77
77
  root = File.expand_path("localized", __dir__)
78
78
 
79
79
  # Select both test links
80
- links = Utopia::Content::Links.index(root, Utopia::Path.create("/"), variant: 'zh')
80
+ links = Utopia::Content::Links.index(root, Utopia::Path.create("/"), locale: 'zh')
81
81
 
82
82
  expect(links.collect(&:title)).to be == ['One', 'Two', 'Three', '四']
83
83
  end
@@ -52,10 +52,10 @@ module Utopia::ContentSpec
52
52
 
53
53
  expect(links.size).to be == 2
54
54
  expect(links[0].name).to be == 'foo'
55
- expect(links[0].variant).to be == 'en'
55
+ expect(links[0].locale).to be == 'en'
56
56
 
57
57
  expect(links[1].name).to be == 'foo'
58
- expect(links[1].variant).to be == 'ja'
58
+ expect(links[1].locale).to be == 'ja'
59
59
  end
60
60
  end
61
61
  end
@@ -0,0 +1,48 @@
1
+ #!/usr/bin/env rspec
2
+ # Copyright, 2014, by Samuel G. D. Williams. <http://www.codeotaku.com>
3
+ #
4
+ # Permission is hereby granted, free of charge, to any person obtaining a copy
5
+ # of this software and associated documentation files (the "Software"), to deal
6
+ # in the Software without restriction, including without limitation the rights
7
+ # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8
+ # copies of the Software, and to permit persons to whom the Software is
9
+ # furnished to do so, subject to the following conditions:
10
+ #
11
+ # The above copyright notice and this permission notice shall be included in
12
+ # all copies or substantial portions of the Software.
13
+ #
14
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15
+ # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16
+ # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17
+ # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18
+ # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19
+ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
20
+ # THE SOFTWARE.
21
+
22
+ require 'utopia/locale'
23
+
24
+ module Utopia::LocaleSpec
25
+ describe Utopia::Locale do
26
+ it "should load from string" do
27
+ locale = Utopia::Locale.load('en-US')
28
+
29
+ expect(locale.language).to be == 'en'
30
+ expect(locale.country).to be == 'US'
31
+ expect(locale.variant).to be == nil
32
+ end
33
+
34
+ it "should load from nil and return nil" do
35
+ expect(Utopia::Locale.load(nil)).to be == nil
36
+ end
37
+
38
+ it "should dump nil and give nil" do
39
+ expect(Utopia::Locale.dump(nil)).to be == nil
40
+ end
41
+
42
+ it "should dump locale and give string" do
43
+ locale = Utopia::Locale.new('en', 'US')
44
+
45
+ expect(Utopia::Locale.dump(locale)).to be == 'en-US'
46
+ end
47
+ end
48
+ end
@@ -24,6 +24,7 @@ require 'rack/test'
24
24
 
25
25
  require 'utopia/static'
26
26
  require 'utopia/content'
27
+ require 'utopia/controller'
27
28
  require 'utopia/localization'
28
29
 
29
30
  module Utopia::StaticSpec
@@ -38,21 +39,52 @@ module Utopia::StaticSpec
38
39
  expect(last_response.body).to be == 'localized.en.txt'
39
40
  end
40
41
 
41
- it "should respond with the requested localization" do
42
+ it "should localize request based on path" do
42
43
  get '/en/localized.txt'
43
44
  expect(last_response.body).to be == 'localized.en.txt'
44
-
45
+
45
46
  get '/de/localized.txt'
46
47
  expect(last_response.body).to be == 'localized.de.txt'
47
-
48
- get '/jp/localized.txt'
49
- expect(last_response.body).to be == 'localized.jp.txt'
48
+
49
+ get '/ja/localized.txt'
50
+ expect(last_response.body).to be == 'localized.ja.txt'
51
+ end
52
+
53
+ it "should localize request based on domain name" do
54
+ get '/localized.txt', {}, 'HTTP_HOST' => 'foobar.com'
55
+ expect(last_response.body).to be == 'localized.en.txt'
56
+
57
+ get '/localized.txt', {}, 'HTTP_HOST' => 'foobar.de'
58
+ expect(last_response.body).to be == 'localized.de.txt'
59
+
60
+ get '/localized.txt', {}, 'HTTP_HOST' => 'foobar.co.jp'
61
+ expect(last_response.body).to be == 'localized.ja.txt'
62
+ end
63
+
64
+ it "should get a non-localized resource" do
65
+ get "/en/test.txt"
66
+ expect(last_response.body).to be == 'Hello World!'
50
67
  end
51
68
 
52
69
  it "should respond with accepted language localization" do
53
- get '/localized.txt', {}, 'HTTP_ACCEPT_LANGUAGE' => 'jp,en'
70
+ get '/localized.txt', {}, 'HTTP_ACCEPT_LANGUAGE' => 'ja,en'
54
71
 
55
- expect(last_response.body).to be == 'localized.jp.txt'
72
+ expect(last_response.body).to be == 'localized.ja.txt'
73
+ end
74
+
75
+ it "should get a list of all localizations" do
76
+ get '/all_locales'
77
+ expect(last_response.body).to be == 'en,ja,de'
78
+ end
79
+
80
+ it "should get the default locale" do
81
+ get '/default_locale'
82
+ expect(last_response.body).to be == 'en'
83
+ end
84
+
85
+ it "should get the current locale (german)" do
86
+ get '/current_locale', {}, 'HTTP_HOST' => 'foobar.de'
87
+ expect(last_response.body).to be == 'de'
56
88
  end
57
89
  end
58
90
  end
@@ -1,11 +1,14 @@
1
1
 
2
+ localization_spec_root = File.expand_path('localization_spec', __dir__)
3
+
2
4
  use Utopia::Localization,
3
- locales: ['en', 'jp', 'de']
5
+ locales: ['en', 'ja', 'de'],
6
+ hosts: {/foobar\.com$/ => 'en', /foobar\.co\.jp$/ => 'ja', /foobar\.de$/ => 'de'}
4
7
 
5
- use Utopia::Static,
6
- root: File.expand_path('../pages', __FILE__)
8
+ use Utopia::Controller,
9
+ root: localization_spec_root
7
10
 
8
- use Utopia::Content,
9
- root: File.expand_path('../pages', __FILE__)
11
+ use Utopia::Static,
12
+ root: localization_spec_root
10
13
 
11
14
  run lambda { |env| [404, {}, []] }
@@ -0,0 +1,18 @@
1
+
2
+ on 'all_locales' do |request, path|
3
+ wrapper = Utopia::Localization[request]
4
+
5
+ succeed! content: wrapper.all_locales.join(',')
6
+ end
7
+
8
+ on 'default_locale' do |request, path|
9
+ wrapper = Utopia::Localization[request]
10
+
11
+ succeed! content: wrapper.default_locale
12
+ end
13
+
14
+ on 'current_locale' do |request, path|
15
+ wrapper = Utopia::Localization[request]
16
+
17
+ succeed! content: wrapper.current_locale
18
+ end
@@ -0,0 +1 @@
1
+ localized.ja.txt
@@ -0,0 +1 @@
1
+ Hello World!
@@ -102,16 +102,16 @@ module Utopia::PathSpec
102
102
  expect{path[0] = 'bob'}.to raise_exception(RuntimeError)
103
103
  end
104
104
 
105
- it "should give the correct variant" do
105
+ it "should give the correct locale" do
106
106
  path = Utopia::Path["foo.en"]
107
107
 
108
- expect(path.basename.variant).to be == 'en'
108
+ expect(path.basename.locale).to be == 'en'
109
109
  end
110
110
 
111
- it "should give no variant" do
111
+ it "should give no locale" do
112
112
  path = Utopia::Path["foo"]
113
113
 
114
- expect(path.basename.variant).to be == nil
114
+ expect(path.basename.locale).to be == nil
115
115
  end
116
116
 
117
117
  it "should expand relative paths" do
@@ -4,15 +4,15 @@ use Utopia::Session::EncryptedCookie, secret: "97111cabf4c1a5e85b8029cf7c61aa444
4
4
  run lambda { |env|
5
5
  request = Rack::Request.new(env)
6
6
 
7
- if env['PATH_INFO'] =~ /login/
7
+ if env[Rack::PATH_INFO] =~ /login/
8
8
  env['rack.session']['login'] = 'true'
9
9
 
10
10
  [200, {}, []]
11
- elsif env['PATH_INFO'] =~ /session-set/
11
+ elsif env[Rack::PATH_INFO] =~ /session-set/
12
12
  env['rack.session'][request[:key]] = request[:value]
13
13
 
14
14
  [200, {}, []]
15
- elsif env['PATH_INFO'] =~ /session-get/
15
+ elsif env[Rack::PATH_INFO] =~ /session-get/
16
16
  [200, {}, [env['rack.session'][request[:key]]]]
17
17
  else
18
18
  [404, {}, []]
data/utopia.gemspec CHANGED
@@ -4,10 +4,10 @@ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
4
  require 'utopia/version'
5
5
 
6
6
  Gem::Specification.new do |spec|
7
- spec.name = "utopia"
7
+ spec.name = 'utopia'
8
8
  spec.version = Utopia::VERSION
9
- spec.authors = ["Samuel Williams"]
10
- spec.email = ["samuel.williams@oriontransfer.co.nz"]
9
+ spec.authors = ['Samuel Williams']
10
+ spec.email = ['samuel.williams@oriontransfer.co.nz']
11
11
  spec.description = <<-EOF
12
12
  Utopia is a website generation framework which provides a robust set of tools
13
13
  to build highly complex dynamic websites. It uses the filesystem heavily for
@@ -15,25 +15,27 @@ Gem::Specification.new do |spec|
15
15
  structure representing the website.
16
16
  EOF
17
17
  spec.summary = %q{Utopia is a framework for building dynamic content-driven websites.}
18
- spec.homepage = "https://github.com/ioquatix/utopia"
18
+ spec.homepage = 'https://github.com/ioquatix/utopia'
19
19
 
20
20
  spec.files = `git ls-files`.split($/)
21
21
  spec.executables = spec.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
22
22
  spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
23
- spec.require_paths = ["lib"]
23
+ spec.require_paths = ['lib']
24
24
 
25
- spec.add_dependency "trenni", "~> 1.4.1"
26
- spec.add_dependency "mime-types", "~> 2.0"
25
+ spec.required_ruby_version = '~> 2.0'
27
26
 
28
- spec.add_dependency "rack", "~> 1.6"
29
- spec.add_dependency "rack-cache", "~> 1.2.0"
27
+ spec.add_dependency 'trenni', '~> 1.4.1'
28
+ spec.add_dependency 'mime-types', '~> 2.0'
30
29
 
31
- spec.add_dependency "mail", "~> 2.6.1"
30
+ spec.add_dependency 'rack', '~> 1.6'
31
+ spec.add_dependency 'rack-cache', '~> 1.2.0'
32
32
 
33
- spec.add_dependency "concurrent-ruby", "~> 1.0.0"
33
+ spec.add_dependency 'mail', '~> 2.6.1'
34
34
 
35
- spec.add_development_dependency "bundler", "~> 1.3"
36
- spec.add_development_dependency "rspec", "~> 3.1.0"
37
- spec.add_development_dependency "puma"
38
- spec.add_development_dependency "rake"
35
+ spec.add_dependency 'concurrent-ruby', '~> 1.0.0'
36
+
37
+ spec.add_development_dependency 'bundler', '~> 1.3'
38
+ spec.add_development_dependency 'rspec', '~> 3.4.0'
39
+ spec.add_development_dependency 'puma'
40
+ spec.add_development_dependency 'rake'
39
41
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: utopia
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.1.4
4
+ version: 1.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Samuel Williams
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2015-12-03 00:00:00.000000000 Z
11
+ date: 2015-12-07 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: trenni
@@ -114,14 +114,14 @@ dependencies:
114
114
  requirements:
115
115
  - - "~>"
116
116
  - !ruby/object:Gem::Version
117
- version: 3.1.0
117
+ version: 3.4.0
118
118
  type: :development
119
119
  prerelease: false
120
120
  version_requirements: !ruby/object:Gem::Requirement
121
121
  requirements:
122
122
  - - "~>"
123
123
  - !ruby/object:Gem::Version
124
- version: 3.1.0
124
+ version: 3.4.0
125
125
  - !ruby/object:Gem::Dependency
126
126
  name: puma
127
127
  requirement: !ruby/object:Gem::Requirement
@@ -187,6 +187,7 @@ files:
187
187
  - lib/utopia/extensions/date.rb
188
188
  - lib/utopia/extensions/rack.rb
189
189
  - lib/utopia/http.rb
190
+ - lib/utopia/locale.rb
190
191
  - lib/utopia/localization.rb
191
192
  - lib/utopia/mail_exceptions.rb
192
193
  - lib/utopia/middleware.rb
@@ -263,17 +264,20 @@ files:
263
264
  - spec/utopia/exception_handler_spec.ru
264
265
  - spec/utopia/exception_handler_spec/controller.rb
265
266
  - spec/utopia/extensions_spec.rb
267
+ - spec/utopia/locale_spec.rb
266
268
  - spec/utopia/localization_spec.rb
267
269
  - spec/utopia/localization_spec.ru
270
+ - spec/utopia/localization_spec/controller.rb
271
+ - spec/utopia/localization_spec/localized.de.txt
272
+ - spec/utopia/localization_spec/localized.en.txt
273
+ - spec/utopia/localization_spec/localized.ja.txt
274
+ - spec/utopia/localization_spec/test.txt
268
275
  - spec/utopia/middleware_spec.rb
269
276
  - spec/utopia/pages/_heading.xnode
270
277
  - spec/utopia/pages/content/_show-value.xnode
271
278
  - spec/utopia/pages/content/links.yaml
272
279
  - spec/utopia/pages/content/test-partial.xnode
273
280
  - spec/utopia/pages/index.xnode
274
- - spec/utopia/pages/localized.de.txt
275
- - spec/utopia/pages/localized.en.txt
276
- - spec/utopia/pages/localized.jp.txt
277
281
  - spec/utopia/pages/node/index.xnode
278
282
  - spec/utopia/pages/test.txt
279
283
  - spec/utopia/path/matcher_spec.rb
@@ -293,9 +297,9 @@ require_paths:
293
297
  - lib
294
298
  required_ruby_version: !ruby/object:Gem::Requirement
295
299
  requirements:
296
- - - ">="
300
+ - - "~>"
297
301
  - !ruby/object:Gem::Version
298
- version: '0'
302
+ version: '2.0'
299
303
  required_rubygems_version: !ruby/object:Gem::Requirement
300
304
  requirements:
301
305
  - - ">="
@@ -348,17 +352,20 @@ test_files:
348
352
  - spec/utopia/exception_handler_spec.ru
349
353
  - spec/utopia/exception_handler_spec/controller.rb
350
354
  - spec/utopia/extensions_spec.rb
355
+ - spec/utopia/locale_spec.rb
351
356
  - spec/utopia/localization_spec.rb
352
357
  - spec/utopia/localization_spec.ru
358
+ - spec/utopia/localization_spec/controller.rb
359
+ - spec/utopia/localization_spec/localized.de.txt
360
+ - spec/utopia/localization_spec/localized.en.txt
361
+ - spec/utopia/localization_spec/localized.ja.txt
362
+ - spec/utopia/localization_spec/test.txt
353
363
  - spec/utopia/middleware_spec.rb
354
364
  - spec/utopia/pages/_heading.xnode
355
365
  - spec/utopia/pages/content/_show-value.xnode
356
366
  - spec/utopia/pages/content/links.yaml
357
367
  - spec/utopia/pages/content/test-partial.xnode
358
368
  - spec/utopia/pages/index.xnode
359
- - spec/utopia/pages/localized.de.txt
360
- - spec/utopia/pages/localized.en.txt
361
- - spec/utopia/pages/localized.jp.txt
362
369
  - spec/utopia/pages/node/index.xnode
363
370
  - spec/utopia/pages/test.txt
364
371
  - spec/utopia/path/matcher_spec.rb
@@ -1 +0,0 @@
1
- localized.jp.txt