utopia 1.9.11 → 2.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.codeclimate.yml +3 -2
- data/.gitignore +4 -1
- data/.rspec +1 -0
- data/.travis.yml +4 -0
- data/.yardopts +2 -0
- data/Gemfile +8 -1
- data/README.md +2 -2
- data/Rakefile +10 -10
- data/benchmarks/call_vs_check.rb +36 -0
- data/benchmarks/const_vs_hash.rb +33 -0
- data/documentation/Gemfile +5 -0
- data/documentation/Guardfile +20 -0
- data/documentation/config.ru +6 -13
- data/documentation/config/puma.rb +20 -0
- data/documentation/pages/_editor.xnode +64 -0
- data/documentation/pages/_heading.xnode +2 -2
- data/documentation/pages/_page.xnode +1 -2
- data/documentation/pages/errors/exception.xnode +3 -3
- data/documentation/pages/errors/file-not-found.xnode +3 -3
- data/documentation/pages/wiki/bower-integration/content.md +1 -1
- data/documentation/pages/wiki/content.md +6 -8
- data/documentation/pages/wiki/controller.rb +3 -3
- data/documentation/pages/wiki/edit.xnode +7 -19
- data/documentation/pages/wiki/middleware/content/content.md +4 -10
- data/documentation/pages/wiki/{controller → middleware/controller}/actions/content.md +0 -0
- data/documentation/pages/wiki/{controller → middleware/controller}/links.yaml +0 -0
- data/documentation/pages/wiki/{controller → middleware/controller}/rewrite/content.md +3 -3
- data/documentation/pages/wiki/show.xnode +4 -6
- data/documentation/pages/wiki/updating-utopia/content.md +55 -0
- data/documentation/pages/wiki/your-first-page/content.md +5 -3
- data/documentation/public/materials +1 -0
- data/lib/utopia.rb +3 -4
- data/lib/utopia/command.rb +4 -284
- data/lib/utopia/command/server.rb +115 -0
- data/lib/utopia/command/setup.rb +78 -0
- data/lib/utopia/command/site.rb +183 -0
- data/lib/utopia/content.rb +83 -59
- data/lib/utopia/content/{transaction.rb → document.rb} +116 -110
- data/lib/utopia/content/link.rb +7 -2
- data/lib/utopia/content/links.rb +2 -1
- data/lib/utopia/content/markup.rb +7 -2
- data/lib/utopia/{tags/deferred.rb → content/namespace.rb} +25 -6
- data/lib/utopia/content/node.rb +74 -76
- data/lib/utopia/content/response.rb +22 -3
- data/lib/utopia/content/tags.rb +66 -0
- data/lib/utopia/controller.rb +10 -18
- data/lib/utopia/controller/actions.rb +10 -0
- data/lib/utopia/controller/base.rb +2 -1
- data/lib/utopia/controller/respond.rb +1 -1
- data/lib/utopia/controller/rewrite.rb +8 -4
- data/lib/utopia/exceptions.rb +1 -0
- data/lib/utopia/exceptions/handler.rb +7 -2
- data/lib/utopia/exceptions/mailer.rb +33 -12
- data/lib/utopia/{tags/node.rb → extensions/array_split.rb} +11 -9
- data/lib/utopia/{tags/environment.rb → extensions/date_comparisons.rb} +24 -14
- data/lib/utopia/http.rb +2 -0
- data/lib/utopia/locale.rb +1 -0
- data/lib/utopia/localization.rb +37 -28
- data/lib/utopia/logger.rb +1 -0
- data/lib/utopia/logger/compact_formatter.rb +1 -0
- data/lib/utopia/middleware.rb +11 -1
- data/lib/utopia/path.rb +1 -0
- data/lib/utopia/path/matcher.rb +14 -2
- data/lib/utopia/redirection.rb +13 -16
- data/lib/utopia/session.rb +14 -6
- data/lib/utopia/setup.rb +3 -1
- data/lib/utopia/static.rb +11 -12
- data/lib/utopia/version.rb +1 -1
- data/setup/server/git/hooks/post-receive +0 -4
- data/setup/site/.gitignore +9 -0
- data/setup/site/.rspec +1 -0
- data/setup/site/Gemfile +4 -0
- data/setup/site/Guardfile +17 -0
- data/setup/site/Rakefile +2 -2
- data/setup/site/config.ru +5 -12
- data/setup/site/pages/_heading.xnode +2 -2
- data/setup/site/pages/_page.xnode +1 -1
- data/setup/site/pages/errors/exception.xnode +3 -3
- data/setup/site/pages/errors/file-not-found.xnode +3 -3
- data/setup/site/pages/welcome/index.xnode +3 -3
- data/setup/site/public/_static/site.css +4 -0
- data/setup/site/spec/spec_helper.rb +29 -0
- data/setup/site/tasks/deploy.rake +13 -0
- data/setup/site/tasks/development.rake +34 -0
- data/setup/site/tasks/environment.rake +17 -0
- data/spec/mock_node.rb +15 -0
- data/spec/spec_helper.rb +29 -0
- data/{lib/utopia/extensions/date.rb → spec/utopia/content/document_spec.rb} +31 -21
- data/spec/utopia/content/markup_spec.rb +2 -2
- data/spec/utopia/content/{tag_spec.rb → namespace_spec.rb} +17 -10
- data/spec/utopia/content/tags_spec.rb +80 -0
- data/spec/utopia/content_spec.rb +1 -1
- data/spec/utopia/content_spec.ru +1 -6
- data/spec/utopia/content_spec/_heading.xnode +1 -1
- data/spec/utopia/content_spec/content/test-partial.xnode +1 -1
- data/spec/utopia/content_spec/index.xnode +1 -1
- data/spec/utopia/controller/middleware_spec.ru +1 -3
- data/spec/utopia/controller/respond_spec.rb +2 -22
- data/spec/utopia/controller/respond_spec.ru +1 -5
- data/spec/utopia/controller/respond_spec/errors/file-not-found.xnode +7 -6
- data/spec/utopia/exceptions/handler_spec.ru +1 -2
- data/spec/utopia/exceptions/mailer_spec.ru +1 -2
- data/spec/utopia/extensions_spec.rb +2 -2
- data/spec/utopia/localization_spec.ru +1 -2
- data/spec/utopia/performance_spec.rb +2 -6
- data/spec/utopia/performance_spec/config.ru +5 -12
- data/spec/utopia/performance_spec/pages/_heading.xnode +2 -2
- data/spec/utopia/performance_spec/pages/_page.xnode +1 -1
- data/spec/utopia/performance_spec/pages/errors/exception.xnode +3 -3
- data/spec/utopia/performance_spec/pages/errors/file-not-found.xnode +3 -3
- data/spec/utopia/performance_spec/pages/welcome/index.xnode +3 -3
- data/spec/utopia/setup_spec.rb +79 -15
- data/utopia.gemspec +3 -3
- metadata +41 -27
- data/.simplecov +0 -9
- data/documentation/pages/welcome/index.xnode +0 -41
- data/lib/utopia/content/tag.rb +0 -90
- data/lib/utopia/extensions/array.rb +0 -29
- data/lib/utopia/tags/override.rb +0 -33
- data/setup/site/.simplecov +0 -9
- data/setup/site/tasks/test.rake +0 -10
- data/setup/site/tasks/utopia.rake +0 -41
- data/spec/utopia/controller/respond_spec/rewrite/controller.rb +0 -12
data/lib/utopia/exceptions.rb
CHANGED
@@ -20,8 +20,9 @@
|
|
20
20
|
|
21
21
|
module Utopia
|
22
22
|
module Exceptions
|
23
|
-
#
|
23
|
+
# A middleware which catches exceptions and performs an internal redirect.
|
24
24
|
class Handler
|
25
|
+
# @param location [String] Peform an internal redirect to this location when an exception is raised.
|
25
26
|
def initialize(app, location = '/errors/exception')
|
26
27
|
@app = app
|
27
28
|
|
@@ -29,6 +30,8 @@ module Utopia
|
|
29
30
|
end
|
30
31
|
|
31
32
|
def freeze
|
33
|
+
return self if frozen?
|
34
|
+
|
32
35
|
@location.freeze
|
33
36
|
|
34
37
|
super
|
@@ -74,7 +77,9 @@ module Utopia
|
|
74
77
|
end
|
75
78
|
end
|
76
79
|
|
77
|
-
private
|
80
|
+
private
|
81
|
+
|
82
|
+
def write_exception_to_stream(stream, env, exception, include_backtrace = false)
|
78
83
|
buffer = []
|
79
84
|
|
80
85
|
buffer << "While requesting resource #{env[Rack::PATH_INFO].inspect}, a fatal error occurred:"
|
@@ -23,7 +23,7 @@ require 'mail'
|
|
23
23
|
|
24
24
|
module Utopia
|
25
25
|
module Exceptions
|
26
|
-
#
|
26
|
+
# A middleware which catches all exceptions raised from the app it wraps and sends a useful email with the exception, stacktrace, and contents of the environment.
|
27
27
|
class Mailer
|
28
28
|
# A basic local non-authenticated SMTP server.
|
29
29
|
LOCAL_SMTP = [:smtp, {
|
@@ -32,17 +32,36 @@ module Utopia
|
|
32
32
|
:enable_starttls_auto => false
|
33
33
|
}]
|
34
34
|
|
35
|
-
|
35
|
+
DEFAULT_FROM = (ENV['USER'] || 'rack') + "@localhost"
|
36
|
+
DEFAULT_SUBJECT = '%{exception} [PID %{pid} : %{cwd}]'
|
37
|
+
|
38
|
+
# @param to [String] The address to email error reports to.
|
39
|
+
# @param from [String] The from address for error reports.
|
40
|
+
# @param subject [String] The subject template which can access attributes defined by `#attributes_for`.
|
41
|
+
# @param delivery_method [Object] The delivery method as required by the mail gem.
|
42
|
+
# @param dump_environment [Boolean] Attach `env` as `environment.yaml` to the error report.
|
43
|
+
def initialize(app, to: "postmaster", from: DEFAULT_FROM, subject: DEFAULT_SUBJECT, delivery_method: LOCAL_SMTP, dump_environment: false)
|
36
44
|
@app = app
|
37
45
|
|
38
|
-
@to =
|
39
|
-
@from =
|
40
|
-
@subject =
|
41
|
-
@delivery_method =
|
46
|
+
@to = to
|
47
|
+
@from = from
|
48
|
+
@subject = subject
|
49
|
+
@delivery_method = delivery_method
|
50
|
+
@dump_environment = dump_environment
|
51
|
+
end
|
52
|
+
|
53
|
+
def freeze
|
54
|
+
return self if frozen?
|
55
|
+
|
56
|
+
@to.freeze
|
57
|
+
@from.freeze
|
58
|
+
@subject.freeze
|
59
|
+
@delivery_method.freeze
|
60
|
+
@dump_environment.freeze
|
42
61
|
|
43
|
-
|
62
|
+
super
|
44
63
|
end
|
45
|
-
|
64
|
+
|
46
65
|
def call(env)
|
47
66
|
begin
|
48
67
|
return @app.call(env)
|
@@ -100,17 +119,19 @@ module Utopia
|
|
100
119
|
return io.string
|
101
120
|
end
|
102
121
|
|
103
|
-
def
|
104
|
-
|
122
|
+
def attributes_for(exception, env)
|
123
|
+
{
|
105
124
|
exception: exception.class.name,
|
106
125
|
pid: $$,
|
107
126
|
cwd: Dir.getwd,
|
108
127
|
}
|
109
|
-
|
128
|
+
end
|
129
|
+
|
130
|
+
def generate_mail(exception, env)
|
110
131
|
mail = Mail.new(
|
111
132
|
:from => @from,
|
112
133
|
:to => @to,
|
113
|
-
:subject => @subject %
|
134
|
+
:subject => @subject % attributes_for(exception, env)
|
114
135
|
)
|
115
136
|
|
116
137
|
mail.text_part = Mail::Part.new
|
@@ -19,15 +19,17 @@
|
|
19
19
|
# THE SOFTWARE.
|
20
20
|
|
21
21
|
module Utopia
|
22
|
-
module
|
23
|
-
|
24
|
-
def
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
22
|
+
module Extensions
|
23
|
+
module ArraySplit
|
24
|
+
def split_at(*args, &block)
|
25
|
+
if middle = index(*args, &block)
|
26
|
+
[self[0...middle], self[middle], self[middle+1..-1]]
|
27
|
+
else
|
28
|
+
[[], nil, []]
|
29
|
+
end
|
30
30
|
end
|
31
31
|
end
|
32
|
+
|
33
|
+
::Array.prepend(ArraySplit)
|
32
34
|
end
|
33
|
-
end
|
35
|
+
end
|
@@ -18,24 +18,34 @@
|
|
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 'date'
|
22
|
+
|
21
23
|
module Utopia
|
22
|
-
module
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
24
|
+
module Extensions
|
25
|
+
# Provides comparison operator extensions.
|
26
|
+
module TimeDateComparison
|
27
|
+
def <=>(other)
|
28
|
+
if Date === other or DateTime === other
|
29
|
+
self.to_datetime <=> other
|
30
|
+
else
|
31
|
+
super
|
32
|
+
end
|
30
33
|
end
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
+
end
|
35
|
+
|
36
|
+
::Time.prepend(TimeDateComparison)
|
34
37
|
|
35
|
-
|
36
|
-
|
38
|
+
# Provides comparison operator extensions.
|
39
|
+
module DateTimeComparison
|
40
|
+
def <=>(other)
|
41
|
+
if Time === other
|
42
|
+
self.to_datetime <=> other.to_datetime
|
43
|
+
else
|
44
|
+
super
|
37
45
|
end
|
38
46
|
end
|
39
47
|
end
|
48
|
+
|
49
|
+
::Date.prepend(DateTimeComparison)
|
40
50
|
end
|
41
|
-
end
|
51
|
+
end
|
data/lib/utopia/http.rb
CHANGED
data/lib/utopia/locale.rb
CHANGED
data/lib/utopia/localization.rb
CHANGED
@@ -21,7 +21,7 @@
|
|
21
21
|
require_relative 'middleware'
|
22
22
|
|
23
23
|
module Utopia
|
24
|
-
#
|
24
|
+
# A middleware which attempts to find localized content.
|
25
25
|
class Localization
|
26
26
|
# A wrapper to provide easy access to locale related data in the request.
|
27
27
|
class Wrapper
|
@@ -59,38 +59,42 @@ module Utopia
|
|
59
59
|
LOCALIZATION_KEY = 'utopia.localization'.freeze
|
60
60
|
CURRENT_LOCALE_KEY = 'utopia.localization.current_locale'.freeze
|
61
61
|
|
62
|
-
|
63
|
-
|
64
|
-
|
62
|
+
# @param locales [Array<String>] An array of all supported locales.
|
63
|
+
# @param default_locale [String] The default locale if none is provided.
|
64
|
+
# @param default_locales [String] The locales to try in order if none is provided.
|
65
|
+
# @param hosts [Hash<Pattern, String>] Specify a mapping of the HTTP_HOST header to a given locale.
|
66
|
+
# @param ignore [Array<Pattern>] A list of patterns matched against PATH_INFO which will not be localized.
|
67
|
+
def initialize(app, locales:, default_locale: nil, default_locales: nil, hosts: {}, ignore: [], **options)
|
65
68
|
@app = app
|
66
69
|
|
67
|
-
@all_locales = HTTP::Accept::Languages::Locales.new(
|
70
|
+
@all_locales = HTTP::Accept::Languages::Locales.new(locales)
|
68
71
|
|
69
|
-
# Locales here are represented as an array of strings, e.g. ['en', 'ja', 'cn', 'de'].
|
70
|
-
unless @default_locales =
|
72
|
+
# Locales here are represented as an array of strings, e.g. ['en', 'ja', 'cn', 'de'] and are used in order if no locale is specified by the user.
|
73
|
+
unless @default_locales = default_locales
|
71
74
|
# We append nil, i.e. no localization.
|
72
75
|
@default_locales = @all_locales.names + [nil]
|
73
76
|
end
|
74
77
|
|
75
|
-
|
76
|
-
@default_locales.unshift(default_locale)
|
77
|
-
else
|
78
|
-
@default_locale = @default_locales.first
|
79
|
-
end
|
78
|
+
@default_locale = default_locale || @default_locales.first
|
80
79
|
|
81
|
-
@
|
80
|
+
unless @default_locales.include? @default_locale
|
81
|
+
@default_locales.unshift(@default_locale)
|
82
|
+
end
|
82
83
|
|
83
|
-
|
84
|
+
# Select a localization based on a request host name:
|
85
|
+
@hosts = hosts
|
84
86
|
|
85
|
-
|
87
|
+
@ignore = ignore || options[:nonlocalized]
|
86
88
|
end
|
87
89
|
|
88
90
|
def freeze
|
91
|
+
return self if frozen?
|
92
|
+
|
89
93
|
@all_locales.freeze
|
90
94
|
@default_locales.freeze
|
91
95
|
@default_locale.freeze
|
92
96
|
@hosts.freeze
|
93
|
-
@
|
97
|
+
@ignore.freeze
|
94
98
|
|
95
99
|
super
|
96
100
|
end
|
@@ -104,7 +108,7 @@ module Utopia
|
|
104
108
|
# Keep track of what locales have been tried:
|
105
109
|
locales = Set.new
|
106
110
|
|
107
|
-
host_preferred_locales(env)
|
111
|
+
host_preferred_locales(env) do |locale|
|
108
112
|
yield env.merge(CURRENT_LOCALE_KEY => locale) if locales.add? locale
|
109
113
|
end
|
110
114
|
|
@@ -124,16 +128,13 @@ module Utopia
|
|
124
128
|
end
|
125
129
|
end
|
126
130
|
|
127
|
-
HTTP_HOST = 'HTTP_HOST'.freeze
|
128
|
-
|
129
131
|
def host_preferred_locales(env)
|
130
132
|
http_host = env[Rack::HTTP_HOST]
|
131
133
|
|
132
|
-
#
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
matching_hosts.flat_map{|host_pattern, locale| locale}
|
134
|
+
# Yield all hosts which match the incoming http_host:
|
135
|
+
@hosts.each do |pattern, locale|
|
136
|
+
yield locale if http_host[pattern]
|
137
|
+
end
|
137
138
|
end
|
138
139
|
|
139
140
|
def request_preferred_locale(env)
|
@@ -163,10 +164,18 @@ module Utopia
|
|
163
164
|
return []
|
164
165
|
end
|
165
166
|
|
166
|
-
|
167
|
+
SAFE_METHODS = ['GET', 'HEAD']
|
168
|
+
|
169
|
+
def localized?(env)
|
170
|
+
# Only SAFE_METHODS can be localized:
|
171
|
+
request_method = env[Rack::REQUEST_METHOD]
|
172
|
+
return false unless SAFE_METHODS.include?(request_method)
|
173
|
+
|
174
|
+
# Ignore requests which match the ignored paths:
|
167
175
|
path_info = env[Rack::PATH_INFO]
|
176
|
+
return false if @ignore.any? { |pattern| path_info[pattern] != nil }
|
168
177
|
|
169
|
-
|
178
|
+
return true
|
170
179
|
end
|
171
180
|
|
172
181
|
# Set the Vary: header on the response to indicate that this response should include the header in the cache key.
|
@@ -190,8 +199,8 @@ module Utopia
|
|
190
199
|
end
|
191
200
|
|
192
201
|
def call(env)
|
193
|
-
# Pass the request through
|
194
|
-
return @app.call(env)
|
202
|
+
# Pass the request through if it shouldn't be localized:
|
203
|
+
return @app.call(env) unless localized?(env)
|
195
204
|
|
196
205
|
env[LOCALIZATION_KEY] = self
|
197
206
|
|
data/lib/utopia/logger.rb
CHANGED
data/lib/utopia/middleware.rb
CHANGED
@@ -24,12 +24,22 @@ require_relative 'http'
|
|
24
24
|
require_relative 'path'
|
25
25
|
|
26
26
|
module Utopia
|
27
|
+
# The default pages path for {Utopia::Content} middleware.
|
27
28
|
PAGES_PATH = 'pages'.freeze
|
28
29
|
|
29
|
-
# This is used for shared controller variables which get consumed by the content middleware
|
30
|
+
# This is used for shared controller variables which get consumed by the content middleware.
|
30
31
|
VARIABLES_KEY = 'utopia.variables'.freeze
|
31
32
|
|
33
|
+
# The default root directory for middleware to operate within, e.g. the web-site directory. Convention over configuration.
|
34
|
+
# @param subdirectory [String] Appended to the default root to make a more specific path.
|
35
|
+
# @param pwd [String] The working directory for the current site.
|
32
36
|
def self.default_root(subdirectory = PAGES_PATH, pwd = Dir.pwd)
|
33
37
|
File.expand_path(subdirectory, pwd)
|
34
38
|
end
|
39
|
+
|
40
|
+
# The same as {default_root} but returns an instance of {Path}.
|
41
|
+
# @return [Path] The path as requested.
|
42
|
+
def self.default_path(*args)
|
43
|
+
Path[default_root(*args)]
|
44
|
+
end
|
35
45
|
end
|
data/lib/utopia/path.rb
CHANGED
data/lib/utopia/path/matcher.rb
CHANGED
@@ -22,14 +22,23 @@ require_relative '../path'
|
|
22
22
|
|
23
23
|
module Utopia
|
24
24
|
class Path
|
25
|
+
# Performs structured, efficient, matches against {Path} instances. Supports regular expressions, type-casts and constants.
|
26
|
+
# @example
|
27
|
+
# path = Utopia::Path['users/20/edit']
|
28
|
+
# matcher = Utopia::Path::Matcher[users: /users/, id: Integer, action: String]
|
29
|
+
# match_data = matcher.match(path)
|
25
30
|
class Matcher
|
31
|
+
# The result of matching against a {Path}.
|
26
32
|
class MatchData
|
27
33
|
def initialize(named_parts, post_match)
|
28
34
|
@named_parts = named_parts
|
29
35
|
@post_match = Path[post_match]
|
30
36
|
end
|
31
37
|
|
38
|
+
# Matched components by name.
|
32
39
|
attr :named_parts
|
40
|
+
|
41
|
+
# Any remaining part past the end of the explicitly matched components.
|
33
42
|
attr :post_match
|
34
43
|
|
35
44
|
def [] key
|
@@ -41,7 +50,7 @@ module Utopia
|
|
41
50
|
end
|
42
51
|
end
|
43
52
|
|
44
|
-
# patterns
|
53
|
+
# @param patterns [Hash<Symbol,Pattern>] An ordered list of things to match.
|
45
54
|
def initialize(patterns = [])
|
46
55
|
@patterns = patterns
|
47
56
|
end
|
@@ -64,14 +73,16 @@ module Utopia
|
|
64
73
|
return nil
|
65
74
|
end
|
66
75
|
|
67
|
-
# This is a path prefix matching algorithm. The pattern is an array of String, Symbol, Regexp, or nil. The
|
76
|
+
# This is a path prefix matching algorithm. The pattern is an array of String, Symbol, Regexp, or nil. The path is an array of String.
|
68
77
|
def match(path)
|
69
78
|
components = path.to_a
|
70
79
|
|
80
|
+
# Can't possibly match if not enough components:
|
71
81
|
return nil if components.size < @patterns.size
|
72
82
|
|
73
83
|
named_parts = {}
|
74
84
|
|
85
|
+
# Try to match each component against the pattern:
|
75
86
|
@patterns.each_with_index do |(key, pattern), index|
|
76
87
|
component = components[index]
|
77
88
|
|
@@ -83,6 +94,7 @@ module Utopia
|
|
83
94
|
if result = pattern.match(component)
|
84
95
|
named_parts[key] = result
|
85
96
|
else
|
97
|
+
# Couldn't match:
|
86
98
|
return nil
|
87
99
|
end
|
88
100
|
else
|