utopia 1.4.0 → 1.5.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.gitignore +1 -0
- data/.travis.yml +5 -1
- data/Gemfile +3 -0
- data/README.md +11 -0
- data/benchmarks/hash_vs_openstruct.rb +52 -0
- data/benchmarks/struct_vs_class.rb +89 -0
- data/bin/utopia +4 -5
- data/lib/utopia.rb +1 -0
- data/lib/utopia/content.rb +24 -15
- data/lib/utopia/content/node.rb +69 -3
- data/lib/utopia/content/processor.rb +32 -5
- data/lib/utopia/content/transaction.rb +138 -147
- data/lib/utopia/content_length.rb +50 -0
- data/lib/utopia/controller.rb +4 -0
- data/lib/utopia/controller/variables.rb +1 -1
- data/lib/utopia/http.rb +2 -0
- data/lib/utopia/localization.rb +4 -8
- data/lib/utopia/path.rb +13 -13
- data/lib/utopia/static.rb +25 -14
- data/lib/utopia/tags/environment.rb +1 -1
- data/lib/utopia/tags/override.rb +1 -1
- data/lib/utopia/version.rb +1 -1
- data/setup/server/git/hooks/post-receive +32 -24
- data/setup/site/Gemfile +8 -0
- data/setup/site/Rakefile +29 -6
- data/setup/site/config.ru +8 -8
- data/setup/site/pages/_heading.xnode +1 -1
- data/setup/site/pages/_page.xnode +2 -2
- data/spec/utopia/content_spec.rb +3 -3
- data/spec/utopia/controller/sequence_spec.rb +1 -1
- data/spec/utopia/controller/variables_spec.rb +1 -1
- data/spec/utopia/pages/node/index.xnode +1 -1
- data/spec/utopia/performance_spec.rb +90 -0
- data/spec/utopia/performance_spec/cache/head/readme.txt +1 -0
- data/spec/utopia/performance_spec/cache/meta/readme.txt +1 -0
- data/spec/utopia/performance_spec/config.ru +39 -0
- data/spec/utopia/performance_spec/lib/readme.txt +1 -0
- data/spec/utopia/performance_spec/pages/_heading.xnode +2 -0
- data/spec/utopia/performance_spec/pages/_page.xnode +26 -0
- data/spec/utopia/performance_spec/pages/api/controller.rb +7 -0
- data/spec/utopia/performance_spec/pages/errors/exception.xnode +5 -0
- data/spec/utopia/performance_spec/pages/errors/file-not-found.xnode +5 -0
- data/spec/utopia/performance_spec/pages/links.yaml +2 -0
- data/spec/utopia/performance_spec/pages/welcome/index.xnode +17 -0
- data/spec/utopia/rack_helper.rb +5 -2
- data/spec/utopia/setup_spec.rb +93 -0
- data/utopia.gemspec +1 -1
- metadata +34 -5
- data/lib/utopia/mail_exceptions.rb +0 -136
@@ -22,6 +22,7 @@ require_relative 'links'
|
|
22
22
|
|
23
23
|
module Utopia
|
24
24
|
class Content
|
25
|
+
# This error is thrown if a tag doesn't match up when parsing the
|
25
26
|
class UnbalancedTagError < StandardError
|
26
27
|
def initialize(tag)
|
27
28
|
@tag = tag
|
@@ -32,114 +33,47 @@ module Utopia
|
|
32
33
|
attr :tag
|
33
34
|
end
|
34
35
|
|
35
|
-
|
36
|
+
DEFERRED_TAG_NAME = "deferred".freeze
|
37
|
+
CONTENT_TAG_NAME = "content".freeze
|
38
|
+
|
39
|
+
# A single request through content middleware. We use a struct to hide instance varibles since we instance_exec within this context.
|
36
40
|
class Transaction
|
37
|
-
#
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
@tags = []
|
46
|
-
@attributes = tag.to_hash
|
47
|
-
|
48
|
-
@content = nil
|
49
|
-
@deferred = []
|
50
|
-
end
|
51
|
-
|
52
|
-
attr :attributes
|
53
|
-
attr :overrides
|
54
|
-
attr :content
|
55
|
-
attr :node
|
56
|
-
attr :tags
|
57
|
-
|
58
|
-
attr :deferred
|
59
|
-
|
60
|
-
def defer(value = nil, &block)
|
61
|
-
@deferred << block
|
41
|
+
# extend Gem::Deprecate
|
42
|
+
|
43
|
+
def initialize(request, response, attributes = {})
|
44
|
+
@request = request
|
45
|
+
@response = response
|
46
|
+
|
47
|
+
@attributes = attributes
|
62
48
|
|
63
|
-
Tag.closed("deferred", :id => @deferred.size - 1).to_html
|
64
|
-
end
|
65
|
-
|
66
|
-
def [](key)
|
67
|
-
@attributes[key.to_s]
|
68
|
-
end
|
69
|
-
|
70
|
-
def call(transaction)
|
71
|
-
@content = @buffer.string
|
72
|
-
@buffer = StringIO.new
|
73
|
-
|
74
|
-
if node.respond_to? :call
|
75
|
-
node.call(transaction, self)
|
76
|
-
else
|
77
|
-
transaction.parse_xml(@content)
|
78
|
-
end
|
79
|
-
|
80
|
-
return @buffer.string
|
81
|
-
end
|
82
|
-
|
83
|
-
def lookup(tag)
|
84
|
-
if override = @overrides[tag.name]
|
85
|
-
if override.respond_to? :call
|
86
|
-
return override.call(tag)
|
87
|
-
elsif String === override
|
88
|
-
return Tag.new(override, tag.attributes)
|
89
|
-
else
|
90
|
-
return override
|
91
|
-
end
|
92
|
-
else
|
93
|
-
return tag
|
94
|
-
end
|
95
|
-
end
|
96
|
-
|
97
|
-
def cdata(text)
|
98
|
-
@buffer.write(text)
|
99
|
-
end
|
100
|
-
|
101
|
-
def markup(text)
|
102
|
-
cdata(text)
|
103
|
-
end
|
104
|
-
|
105
|
-
def tag_complete(tag)
|
106
|
-
tag.write_full_html(@buffer)
|
107
|
-
end
|
108
|
-
|
109
|
-
def tag_begin(tag)
|
110
|
-
@tags << tag
|
111
|
-
tag.write_open_html(@buffer)
|
112
|
-
end
|
113
|
-
|
114
|
-
def tag_end(tag)
|
115
|
-
raise UnbalancedTagError(tag) unless @tags.pop.name == tag.name
|
116
|
-
|
117
|
-
tag.write_close_html(@buffer)
|
118
|
-
end
|
119
|
-
end
|
120
|
-
|
121
|
-
def initialize(request, response)
|
122
49
|
@begin_tags = []
|
123
50
|
@end_tags = []
|
124
|
-
|
125
|
-
@request = request
|
126
|
-
@response = response
|
127
51
|
end
|
128
|
-
|
129
|
-
attr :request
|
130
|
-
attr :response
|
131
52
|
|
132
53
|
# A helper method for accessing controller variables from view:
|
133
54
|
def controller
|
134
|
-
@request
|
55
|
+
@controller ||= Utopia::Controller[request]
|
56
|
+
end
|
57
|
+
|
58
|
+
def localization
|
59
|
+
@localization ||= Utopia::Localization[request]
|
135
60
|
end
|
136
61
|
|
137
|
-
def
|
138
|
-
Processor.
|
62
|
+
def parse_markup(markup)
|
63
|
+
Processor.parse_markup(markup, self)
|
139
64
|
end
|
140
65
|
|
66
|
+
# The Rack::Request for this transaction.
|
67
|
+
attr :request
|
68
|
+
|
69
|
+
# The mutable Rack::Response for this transaction.
|
70
|
+
attr :response
|
71
|
+
|
72
|
+
# Per-transaction global attributes.
|
73
|
+
attr :attributes
|
74
|
+
|
141
75
|
# Begin tags represents a list from outer to inner most tag.
|
142
|
-
# At any point in parsing
|
76
|
+
# At any point in parsing markup, begin_tags is a list of the inner most tag,
|
143
77
|
# then the next outer tag, etc. This list is used for doing dependent lookups.
|
144
78
|
attr :begin_tags
|
145
79
|
|
@@ -147,30 +81,6 @@ module Utopia
|
|
147
81
|
# have appeared when evaluating nodes.
|
148
82
|
attr :end_tags
|
149
83
|
|
150
|
-
def attributes
|
151
|
-
return current.attributes
|
152
|
-
end
|
153
|
-
|
154
|
-
def localization
|
155
|
-
@localization ||= Utopia::Localization[request]
|
156
|
-
end
|
157
|
-
|
158
|
-
def current
|
159
|
-
@begin_tags[-1]
|
160
|
-
end
|
161
|
-
|
162
|
-
def content
|
163
|
-
@end_tags[-1].content
|
164
|
-
end
|
165
|
-
|
166
|
-
def parent
|
167
|
-
end_tags[-2]
|
168
|
-
end
|
169
|
-
|
170
|
-
def first
|
171
|
-
@begin_tags[0]
|
172
|
-
end
|
173
|
-
|
174
84
|
def tag(name, attributes = {}, &block)
|
175
85
|
tag = Tag.new(name, attributes)
|
176
86
|
|
@@ -182,7 +92,7 @@ module Utopia
|
|
182
92
|
end
|
183
93
|
|
184
94
|
def tag_complete(tag, node = nil)
|
185
|
-
if tag.name ==
|
95
|
+
if tag.name == CONTENT_TAG_NAME
|
186
96
|
current.markup(content)
|
187
97
|
else
|
188
98
|
node ||= lookup(tag)
|
@@ -201,7 +111,7 @@ module Utopia
|
|
201
111
|
|
202
112
|
if node
|
203
113
|
state = State.new(tag, node)
|
204
|
-
|
114
|
+
self.begin_tags << state
|
205
115
|
|
206
116
|
if node.respond_to? :tag_begin
|
207
117
|
node.tag_begin(self, state)
|
@@ -219,31 +129,21 @@ module Utopia
|
|
219
129
|
current.cdata(text)
|
220
130
|
end
|
221
131
|
|
222
|
-
def partial(*args, &block)
|
223
|
-
if block_given?
|
224
|
-
current.defer(&block)
|
225
|
-
else
|
226
|
-
current.defer do
|
227
|
-
tag(*args)
|
228
|
-
end
|
229
|
-
end
|
230
|
-
end
|
231
|
-
|
232
|
-
alias deferred_tag partial
|
233
|
-
|
234
132
|
def tag_end(tag = nil)
|
133
|
+
# Get the current tag which we are completing/ending:
|
235
134
|
top = current
|
236
|
-
|
135
|
+
|
136
|
+
|
237
137
|
if top.tags.empty?
|
238
138
|
if top.node.respond_to? :tag_end
|
239
139
|
top.node.tag_end(self, top)
|
240
140
|
end
|
241
141
|
|
242
|
-
|
142
|
+
self.end_tags << top
|
243
143
|
buffer = top.call(self)
|
244
144
|
|
245
|
-
|
246
|
-
|
145
|
+
self.begin_tags.pop
|
146
|
+
self.end_tags.pop
|
247
147
|
|
248
148
|
if current
|
249
149
|
current.markup(buffer)
|
@@ -258,8 +158,7 @@ module Utopia
|
|
258
158
|
end
|
259
159
|
|
260
160
|
def render_node(node, attributes = {})
|
261
|
-
|
262
|
-
@begin_tags << state
|
161
|
+
self.begin_tags << State.new(attributes, node)
|
263
162
|
|
264
163
|
return tag_end
|
265
164
|
end
|
@@ -269,29 +168,121 @@ module Utopia
|
|
269
168
|
result = tag
|
270
169
|
node = nil
|
271
170
|
|
272
|
-
|
171
|
+
self.begin_tags.reverse_each do |state|
|
273
172
|
result = state.lookup(result)
|
274
173
|
|
275
174
|
node ||= state.node if state.node.respond_to? :lookup
|
276
175
|
|
277
|
-
return result if Node
|
176
|
+
return result if result.is_a?(Node)
|
278
177
|
end
|
279
178
|
|
280
|
-
|
179
|
+
self.end_tags.reverse_each do |state|
|
281
180
|
return state.node.lookup(result) if state.node.respond_to? :lookup
|
282
181
|
end
|
283
182
|
|
284
183
|
return nil
|
285
184
|
end
|
185
|
+
|
186
|
+
# The current tag being processed/rendered. Prefer to access state directly.
|
187
|
+
def current
|
188
|
+
@begin_tags.last
|
189
|
+
end
|
190
|
+
|
191
|
+
# The content of the node
|
192
|
+
def content
|
193
|
+
@end_tags.last.content
|
194
|
+
end
|
195
|
+
|
196
|
+
def parent
|
197
|
+
@end_tags[-2]
|
198
|
+
end
|
199
|
+
|
200
|
+
def first
|
201
|
+
@begin_tags.first
|
202
|
+
end
|
203
|
+
end
|
204
|
+
|
205
|
+
# The state of a single tag being rendered within a Transaction instance.
|
206
|
+
class Transaction::State
|
207
|
+
def initialize(tag, node, attributes = tag.to_hash)
|
208
|
+
@node = node
|
209
|
+
|
210
|
+
@buffer = StringIO.new
|
211
|
+
@overrides = {}
|
212
|
+
|
213
|
+
@tags = []
|
214
|
+
@attributes = attributes
|
215
|
+
|
216
|
+
@content = nil
|
217
|
+
@deferred = []
|
218
|
+
end
|
219
|
+
|
220
|
+
attr :attributes
|
221
|
+
attr :overrides
|
222
|
+
attr :content
|
223
|
+
attr :node
|
224
|
+
attr :tags
|
225
|
+
|
226
|
+
attr :deferred
|
227
|
+
|
228
|
+
def defer(value = nil, &block)
|
229
|
+
@deferred << block
|
230
|
+
|
231
|
+
Tag.closed(DEFERRED_TAG_NAME, :id => @deferred.size - 1).to_html
|
232
|
+
end
|
233
|
+
|
234
|
+
def [](key)
|
235
|
+
@attributes[key]
|
236
|
+
end
|
286
237
|
|
287
|
-
def
|
288
|
-
@
|
289
|
-
if
|
290
|
-
return
|
238
|
+
def lookup(tag)
|
239
|
+
if override = @overrides[tag.name]
|
240
|
+
if override.respond_to? :call
|
241
|
+
return override.call(tag)
|
242
|
+
elsif String === override
|
243
|
+
return Tag.new(override, tag.attributes)
|
244
|
+
else
|
245
|
+
return override
|
291
246
|
end
|
247
|
+
else
|
248
|
+
return tag
|
292
249
|
end
|
250
|
+
end
|
251
|
+
|
252
|
+
def call(transaction)
|
253
|
+
@content = @buffer.string
|
254
|
+
@buffer = StringIO.new
|
255
|
+
|
256
|
+
if node.respond_to? :call
|
257
|
+
node.call(transaction, self)
|
258
|
+
else
|
259
|
+
transaction.parse_markup(@content)
|
260
|
+
end
|
261
|
+
|
262
|
+
return @buffer.string
|
263
|
+
end
|
264
|
+
|
265
|
+
def cdata(text)
|
266
|
+
@buffer.write(text)
|
267
|
+
end
|
268
|
+
|
269
|
+
def markup(text)
|
270
|
+
cdata(text)
|
271
|
+
end
|
272
|
+
|
273
|
+
def tag_complete(tag)
|
274
|
+
tag.write_full_html(@buffer)
|
275
|
+
end
|
276
|
+
|
277
|
+
def tag_begin(tag)
|
278
|
+
@tags << tag
|
279
|
+
tag.write_open_html(@buffer)
|
280
|
+
end
|
281
|
+
|
282
|
+
def tag_end(tag)
|
283
|
+
raise UnbalancedTagError(tag) unless @tags.pop.name == tag.name
|
293
284
|
|
294
|
-
|
285
|
+
tag.write_close_html(@buffer)
|
295
286
|
end
|
296
287
|
end
|
297
288
|
end
|
@@ -0,0 +1,50 @@
|
|
1
|
+
# Copyright, 2016, by Samuel G. D. Williams. <http://www.codeotaku.com>
|
2
|
+
#
|
3
|
+
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
4
|
+
# of this software and associated documentation files (the "Software"), to deal
|
5
|
+
# in the Software without restriction, including without limitation the rights
|
6
|
+
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
7
|
+
# copies of the Software, and to permit persons to whom the Software is
|
8
|
+
# furnished to do so, subject to the following conditions:
|
9
|
+
#
|
10
|
+
# The above copyright notice and this permission notice shall be included in
|
11
|
+
# all copies or substantial portions of the Software.
|
12
|
+
#
|
13
|
+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
14
|
+
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
15
|
+
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
16
|
+
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
17
|
+
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
18
|
+
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
19
|
+
# THE SOFTWARE.
|
20
|
+
|
21
|
+
require_relative 'middleware'
|
22
|
+
|
23
|
+
module Utopia
|
24
|
+
# A faster implementation of Rack::ContentLength which doesn't rewrite body, but does expect it to either be an Array or an object that responds to #bytesize.
|
25
|
+
class ContentLength
|
26
|
+
def initialize(app)
|
27
|
+
@app = app
|
28
|
+
end
|
29
|
+
|
30
|
+
def content_length_of(body)
|
31
|
+
if body.is_a? Array
|
32
|
+
return body.map(&:bytesize).reduce(0, :+)
|
33
|
+
else
|
34
|
+
return body.bytesize
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
def call(env)
|
39
|
+
response = @app.call(env)
|
40
|
+
|
41
|
+
unless response[2].empty? or response[1].include?(Rack::CONTENT_LENGTH)
|
42
|
+
if content_length = self.content_length_of(response[2])
|
43
|
+
response[1][Rack::CONTENT_LENGTH] = content_length
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
return response
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
data/lib/utopia/controller.rb
CHANGED
@@ -47,6 +47,10 @@ module Utopia
|
|
47
47
|
class Controller
|
48
48
|
CONTROLLER_RB = 'controller.rb'.freeze
|
49
49
|
|
50
|
+
def self.[] request
|
51
|
+
request.env[VARIABLES_KEY]
|
52
|
+
end
|
53
|
+
|
50
54
|
def initialize(app, root: nil, cache_controllers: false)
|
51
55
|
@app = app
|
52
56
|
@root = root || Utopia::default_root
|
data/lib/utopia/http.rb
CHANGED
@@ -45,6 +45,7 @@ module Utopia
|
|
45
45
|
:unsupported_method => 405,
|
46
46
|
:gone => 410,
|
47
47
|
:teapot => 418,
|
48
|
+
:unprocessible => 422, # The best status code for a client-side ArgumentError.
|
48
49
|
:error => 500,
|
49
50
|
:unimplemented => 501,
|
50
51
|
:unavailable => 503
|
@@ -79,6 +80,7 @@ module Utopia
|
|
79
80
|
409 => 'Request Conflict'.freeze,
|
80
81
|
410 => 'Resource Removed'.freeze,
|
81
82
|
416 => 'Byte range unsatisfiable'.freeze,
|
83
|
+
422 => 'Unprocessible Entity'.freeze,
|
82
84
|
500 => 'Internal Server Error'.freeze,
|
83
85
|
501 => 'Not Implemented'.freeze,
|
84
86
|
503 => 'Service Unavailable'.freeze
|