utopia 1.1.4 → 1.2.0

Sign up to get free protection for your applications and to get access to all the features.
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