utopia 0.9.17
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.
- data/ext/utopia/xnode/fast_scanner/extconf.rb +6 -0
- data/ext/utopia/xnode/fast_scanner/parser.c +289 -0
- data/lib/utopia.rb +2 -0
- data/lib/utopia/etanni.rb +96 -0
- data/lib/utopia/extensions.rb +87 -0
- data/lib/utopia/link.rb +243 -0
- data/lib/utopia/middleware.rb +33 -0
- data/lib/utopia/middleware/all.rb +24 -0
- data/lib/utopia/middleware/benchmark.rb +47 -0
- data/lib/utopia/middleware/content.rb +139 -0
- data/lib/utopia/middleware/content/node.rb +363 -0
- data/lib/utopia/middleware/controller.rb +198 -0
- data/lib/utopia/middleware/directory_index.rb +54 -0
- data/lib/utopia/middleware/localization.rb +94 -0
- data/lib/utopia/middleware/localization/name.rb +64 -0
- data/lib/utopia/middleware/logger.rb +68 -0
- data/lib/utopia/middleware/redirector.rb +171 -0
- data/lib/utopia/middleware/requester.rb +116 -0
- data/lib/utopia/middleware/static.rb +186 -0
- data/lib/utopia/path.rb +193 -0
- data/lib/utopia/response_helper.rb +22 -0
- data/lib/utopia/session/encrypted_cookie.rb +115 -0
- data/lib/utopia/tag.rb +84 -0
- data/lib/utopia/tags.rb +32 -0
- data/lib/utopia/tags/all.rb +25 -0
- data/lib/utopia/tags/env.rb +24 -0
- data/lib/utopia/tags/fortune.rb +20 -0
- data/lib/utopia/tags/gallery.rb +175 -0
- data/lib/utopia/tags/google_analytics.rb +37 -0
- data/lib/utopia/tags/node.rb +24 -0
- data/lib/utopia/tags/override.rb +28 -0
- data/lib/utopia/time_store.rb +102 -0
- data/lib/utopia/version.rb +24 -0
- data/lib/utopia/xnode.rb +17 -0
- data/lib/utopia/xnode/processor.rb +97 -0
- data/lib/utopia/xnode/scanner.rb +153 -0
- metadata +168 -0
@@ -0,0 +1,363 @@
|
|
1
|
+
# Copyright (c) 2010 Samuel Williams. Released under the GNU GPLv3.
|
2
|
+
#
|
3
|
+
# This program is free software: you can redistribute it and/or modify
|
4
|
+
# it under the terms of the GNU General Public License as published by
|
5
|
+
# the Free Software Foundation, either version 3 of the License, or
|
6
|
+
# (at your option) any later version.
|
7
|
+
#
|
8
|
+
# This program is distributed in the hope that it will be useful,
|
9
|
+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
10
|
+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
11
|
+
# GNU General Public License for more details.
|
12
|
+
#
|
13
|
+
# You should have received a copy of the GNU General Public License
|
14
|
+
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
15
|
+
|
16
|
+
require 'set'
|
17
|
+
|
18
|
+
require 'utopia/xnode'
|
19
|
+
require 'utopia/link'
|
20
|
+
|
21
|
+
module Utopia
|
22
|
+
|
23
|
+
module Middleware
|
24
|
+
class Content
|
25
|
+
class UnbalancedTagError < StandardError
|
26
|
+
def initialize(tag)
|
27
|
+
@tag = tag
|
28
|
+
|
29
|
+
super("Unbalanced tag #{tag.name}")
|
30
|
+
end
|
31
|
+
|
32
|
+
attr :tag
|
33
|
+
end
|
34
|
+
|
35
|
+
# Nodes typically represent XNODE files on the disk.
|
36
|
+
# You can get a list of Links from a current directory. This comprises of all
|
37
|
+
# files ending in ".xnode".
|
38
|
+
|
39
|
+
class Transaction
|
40
|
+
class State
|
41
|
+
def initialize(tag, node)
|
42
|
+
@node = node
|
43
|
+
|
44
|
+
@buffer = StringIO.new
|
45
|
+
@overrides = {}
|
46
|
+
|
47
|
+
@tags = []
|
48
|
+
@attributes = tag.to_hash
|
49
|
+
|
50
|
+
@content = nil
|
51
|
+
end
|
52
|
+
|
53
|
+
attr :attributes
|
54
|
+
attr :overrides
|
55
|
+
attr :content
|
56
|
+
attr :node
|
57
|
+
attr :tags
|
58
|
+
|
59
|
+
def [](key)
|
60
|
+
@attributes[key.to_s]
|
61
|
+
end
|
62
|
+
|
63
|
+
def call(transaction)
|
64
|
+
@content = @buffer.string
|
65
|
+
@buffer = StringIO.new
|
66
|
+
|
67
|
+
if node.respond_to? :call
|
68
|
+
node.call(transaction, self)
|
69
|
+
else
|
70
|
+
transaction.parse_xml(@content)
|
71
|
+
end
|
72
|
+
|
73
|
+
return @buffer.string
|
74
|
+
end
|
75
|
+
|
76
|
+
def lookup(tag)
|
77
|
+
if override = @overrides[tag.name]
|
78
|
+
if override.respond_to? :call
|
79
|
+
return override.call(tag)
|
80
|
+
elsif String === override
|
81
|
+
return Tag.new(override, tag.attributes)
|
82
|
+
else
|
83
|
+
return override
|
84
|
+
end
|
85
|
+
else
|
86
|
+
return tag
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
def cdata(text)
|
91
|
+
@buffer.write(text)
|
92
|
+
end
|
93
|
+
|
94
|
+
def markup(text)
|
95
|
+
cdata(text)
|
96
|
+
end
|
97
|
+
|
98
|
+
def tag_complete(tag)
|
99
|
+
tag.write_full_html(@buffer)
|
100
|
+
end
|
101
|
+
|
102
|
+
def tag_begin(tag)
|
103
|
+
@tags << tag
|
104
|
+
tag.write_open_html(@buffer)
|
105
|
+
end
|
106
|
+
|
107
|
+
def tag_end(tag)
|
108
|
+
raise UnbalancedTagError(tag) unless @tags.pop.name == tag.name
|
109
|
+
|
110
|
+
tag.write_close_html(@buffer)
|
111
|
+
end
|
112
|
+
end
|
113
|
+
|
114
|
+
def initialize(request, response)
|
115
|
+
@begin_tags = []
|
116
|
+
@end_tags = []
|
117
|
+
|
118
|
+
@request = request
|
119
|
+
@response = response
|
120
|
+
end
|
121
|
+
|
122
|
+
def binding
|
123
|
+
super
|
124
|
+
end
|
125
|
+
|
126
|
+
def parse_xml(xml_data)
|
127
|
+
XNode::Processor.new(xml_data, self).parse
|
128
|
+
end
|
129
|
+
|
130
|
+
attr :request
|
131
|
+
attr :response
|
132
|
+
|
133
|
+
# Begin tags represents a list from outer to inner most tag.
|
134
|
+
# At any point in parsing xml, begin_tags is a list of the inner most tag,
|
135
|
+
# then the next outer tag, etc. This list is used for doing dependent lookups.
|
136
|
+
attr :begin_tags
|
137
|
+
|
138
|
+
# End tags represents a list of execution order. This is the order that end tags
|
139
|
+
# have appeared when evaluating nodes.
|
140
|
+
attr :end_tags
|
141
|
+
|
142
|
+
def attributes
|
143
|
+
return current.attributes
|
144
|
+
end
|
145
|
+
|
146
|
+
def current
|
147
|
+
@begin_tags[-1]
|
148
|
+
end
|
149
|
+
|
150
|
+
def content
|
151
|
+
@end_tags[-1].content
|
152
|
+
end
|
153
|
+
|
154
|
+
def parent
|
155
|
+
# @begin_tags[-2]
|
156
|
+
end_tags[-2]
|
157
|
+
end
|
158
|
+
|
159
|
+
def first
|
160
|
+
@begin_tags[0]
|
161
|
+
end
|
162
|
+
|
163
|
+
def tag(name, attributes, &block)
|
164
|
+
tag = Tag.new(name, attributes)
|
165
|
+
|
166
|
+
node = tag_begin(tag)
|
167
|
+
|
168
|
+
yield node if block_given?
|
169
|
+
|
170
|
+
tag_end(tag)
|
171
|
+
end
|
172
|
+
|
173
|
+
def tag_complete(tag, node = nil)
|
174
|
+
if tag.name == "content"
|
175
|
+
current.markup(content)
|
176
|
+
else
|
177
|
+
node ||= lookup(tag)
|
178
|
+
|
179
|
+
if node
|
180
|
+
tag_begin(tag, node)
|
181
|
+
tag_end(tag)
|
182
|
+
else
|
183
|
+
current.tag_complete(tag)
|
184
|
+
end
|
185
|
+
end
|
186
|
+
end
|
187
|
+
|
188
|
+
def tag_begin(tag, node = nil)
|
189
|
+
# LOG.debug("tag_begin: #{tag}")
|
190
|
+
node ||= lookup(tag)
|
191
|
+
|
192
|
+
if node
|
193
|
+
state = State.new(tag, node)
|
194
|
+
@begin_tags << state
|
195
|
+
|
196
|
+
if node.respond_to? :tag_begin
|
197
|
+
node.tag_begin(self, state)
|
198
|
+
end
|
199
|
+
|
200
|
+
return node
|
201
|
+
end
|
202
|
+
|
203
|
+
current.tag_begin(tag)
|
204
|
+
|
205
|
+
return nil
|
206
|
+
end
|
207
|
+
|
208
|
+
def cdata(text)
|
209
|
+
# LOG.debug("cdata: #{text}")
|
210
|
+
current.cdata(text)
|
211
|
+
end
|
212
|
+
|
213
|
+
def tag_end(tag = nil)
|
214
|
+
top = current
|
215
|
+
|
216
|
+
if top.tags.empty?
|
217
|
+
# LOG.debug("tag_end: #{top.inspect}")
|
218
|
+
|
219
|
+
if top.node.respond_to? :tag_end
|
220
|
+
top.node.tag_end(self, top)
|
221
|
+
end
|
222
|
+
|
223
|
+
@end_tags << top
|
224
|
+
buffer = top.call(self)
|
225
|
+
|
226
|
+
@begin_tags.pop
|
227
|
+
@end_tags.pop
|
228
|
+
|
229
|
+
if current
|
230
|
+
current.markup(buffer)
|
231
|
+
end
|
232
|
+
|
233
|
+
return buffer
|
234
|
+
else
|
235
|
+
# LOG.debug("tag_end: #{tag}")
|
236
|
+
current.tag_end(tag)
|
237
|
+
end
|
238
|
+
|
239
|
+
return nil
|
240
|
+
end
|
241
|
+
|
242
|
+
def render_node(node, attributes = {})
|
243
|
+
state = State.new(attributes, node)
|
244
|
+
@begin_tags << state
|
245
|
+
|
246
|
+
return tag_end
|
247
|
+
end
|
248
|
+
|
249
|
+
def lookup(tag)
|
250
|
+
result = tag
|
251
|
+
node = nil
|
252
|
+
|
253
|
+
@begin_tags.reverse_each do |state|
|
254
|
+
result = state.lookup(result)
|
255
|
+
|
256
|
+
node ||= state.node if state.node.respond_to? :lookup
|
257
|
+
|
258
|
+
return result if Node === result
|
259
|
+
end
|
260
|
+
|
261
|
+
@end_tags.reverse_each do |state|
|
262
|
+
return state.node.lookup(result) if state.node.respond_to? :lookup
|
263
|
+
end
|
264
|
+
|
265
|
+
return nil
|
266
|
+
end
|
267
|
+
|
268
|
+
def method_missing(name, *args)
|
269
|
+
@begin_tags.reverse_each do |state|
|
270
|
+
if state.node.respond_to? name
|
271
|
+
return state.node.send(name, *args)
|
272
|
+
end
|
273
|
+
end
|
274
|
+
|
275
|
+
super
|
276
|
+
end
|
277
|
+
end
|
278
|
+
|
279
|
+
class Node
|
280
|
+
def initialize(controller, uri_path, request_path, file_path)
|
281
|
+
@controller = controller
|
282
|
+
|
283
|
+
@uri_path = uri_path
|
284
|
+
@request_path = request_path
|
285
|
+
@file_path = file_path
|
286
|
+
end
|
287
|
+
|
288
|
+
attr :request_path
|
289
|
+
attr :uri_path
|
290
|
+
attr :file_path
|
291
|
+
|
292
|
+
def link
|
293
|
+
return Link.new(:file, uri_path)
|
294
|
+
end
|
295
|
+
|
296
|
+
def lookup_node(path)
|
297
|
+
@controller.lookup_node(path)
|
298
|
+
end
|
299
|
+
|
300
|
+
def local_path(path, base = nil)
|
301
|
+
path = Path.create(path)
|
302
|
+
|
303
|
+
if path.absolute?
|
304
|
+
return File.join(@controller.root, path.components)
|
305
|
+
else
|
306
|
+
base ||= uri_path.dirname
|
307
|
+
return File.join(@controller.root, (base + path).components)
|
308
|
+
end
|
309
|
+
end
|
310
|
+
|
311
|
+
def lookup(tag)
|
312
|
+
return @controller.lookup_tag(tag.name, parent_path)
|
313
|
+
end
|
314
|
+
|
315
|
+
def parent_path
|
316
|
+
uri_path.dirname
|
317
|
+
end
|
318
|
+
|
319
|
+
def links(path, options = {}, &block)
|
320
|
+
path = uri_path.dirname + Path.create(path)
|
321
|
+
links = Links.index(@controller.root, path, options)
|
322
|
+
|
323
|
+
if block_given?
|
324
|
+
links.each &block
|
325
|
+
else
|
326
|
+
links
|
327
|
+
end
|
328
|
+
end
|
329
|
+
|
330
|
+
def related_links
|
331
|
+
name = @uri_path.basename.split(".").first
|
332
|
+
links = Links.index(@controller.root, uri_path.dirname, :name => name, :indices => true)
|
333
|
+
end
|
334
|
+
|
335
|
+
def siblings_path
|
336
|
+
name = @uri_path.basename.split(".").first
|
337
|
+
|
338
|
+
if name == "index"
|
339
|
+
@uri_path.dirname(2)
|
340
|
+
else
|
341
|
+
@uri_path.dirname
|
342
|
+
end
|
343
|
+
end
|
344
|
+
|
345
|
+
def sibling_links(options = {})
|
346
|
+
return Links.index(@controller.root, siblings_path, options)
|
347
|
+
end
|
348
|
+
|
349
|
+
def call(transaction, state)
|
350
|
+
xml_data = @controller.fetch_xml(@file_path).result(transaction.binding)
|
351
|
+
|
352
|
+
transaction.parse_xml(xml_data)
|
353
|
+
end
|
354
|
+
|
355
|
+
def process!(request, response)
|
356
|
+
transaction = Transaction.new(request, response)
|
357
|
+
response.body = [transaction.render_node(self)]
|
358
|
+
end
|
359
|
+
end
|
360
|
+
|
361
|
+
end
|
362
|
+
end
|
363
|
+
end
|
@@ -0,0 +1,198 @@
|
|
1
|
+
# Copyright (c) 2010 Samuel Williams. Released under the GNU GPLv3.
|
2
|
+
#
|
3
|
+
# This program is free software: you can redistribute it and/or modify
|
4
|
+
# it under the terms of the GNU General Public License as published by
|
5
|
+
# the Free Software Foundation, either version 3 of the License, or
|
6
|
+
# (at your option) any later version.
|
7
|
+
#
|
8
|
+
# This program is distributed in the hope that it will be useful,
|
9
|
+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
10
|
+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
11
|
+
# GNU General Public License for more details.
|
12
|
+
#
|
13
|
+
# You should have received a copy of the GNU General Public License
|
14
|
+
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
15
|
+
|
16
|
+
require 'utopia/middleware'
|
17
|
+
require 'utopia/path'
|
18
|
+
|
19
|
+
class Rack::Request
|
20
|
+
def controller(&block)
|
21
|
+
if block_given?
|
22
|
+
env["utopia.controller"].instance_eval(&block)
|
23
|
+
else
|
24
|
+
env["utopia.controller"]
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
module Utopia
|
30
|
+
module Middleware
|
31
|
+
|
32
|
+
class Controller
|
33
|
+
CONTROLLER_RB = "controller.rb"
|
34
|
+
|
35
|
+
class Variables
|
36
|
+
def [](key)
|
37
|
+
instance_variable_get("@#{key}")
|
38
|
+
end
|
39
|
+
|
40
|
+
def []=(key, value)
|
41
|
+
instance_variable_set("@#{key}", value)
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
class Base
|
46
|
+
def initialize(controller)
|
47
|
+
@controller = controller
|
48
|
+
@actions = {}
|
49
|
+
|
50
|
+
methods.each do |method_name|
|
51
|
+
next unless method_name.match(/on_(.*)$/)
|
52
|
+
|
53
|
+
action($1.split("_")) do |path, request|
|
54
|
+
# LOG.debug("Controller: #{method_name}")
|
55
|
+
self.send(method_name, path, request)
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
def action(path, options = {}, &block)
|
61
|
+
cur = @actions
|
62
|
+
|
63
|
+
path.reverse.each do |name|
|
64
|
+
cur = cur[name] ||= {}
|
65
|
+
end
|
66
|
+
|
67
|
+
cur[:action] = Proc.new(&block)
|
68
|
+
end
|
69
|
+
|
70
|
+
def lookup(path)
|
71
|
+
cur = @actions
|
72
|
+
|
73
|
+
path.components.reverse.each do |name|
|
74
|
+
cur = cur[name]
|
75
|
+
|
76
|
+
return nil if cur == nil
|
77
|
+
|
78
|
+
if action = cur[:action]
|
79
|
+
return action
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
# Given a request, call an associated action if one exists
|
85
|
+
def passthrough(path, request)
|
86
|
+
action = lookup(path)
|
87
|
+
|
88
|
+
if action
|
89
|
+
action.call(path, request)
|
90
|
+
else
|
91
|
+
return nil
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
95
|
+
def permission_denied
|
96
|
+
[403, {}, ["Permission Denied!"]]
|
97
|
+
end
|
98
|
+
|
99
|
+
def call(env)
|
100
|
+
@controller.app.call(env)
|
101
|
+
end
|
102
|
+
|
103
|
+
def redirect(target, status=302)
|
104
|
+
Rack::Response.new([], status, "Location" => target.to_s).finish
|
105
|
+
end
|
106
|
+
|
107
|
+
def permission_denied
|
108
|
+
[403, {}, ["Permission Denied!"]]
|
109
|
+
end
|
110
|
+
|
111
|
+
def process!(path, request)
|
112
|
+
end
|
113
|
+
|
114
|
+
def self.require_local(path)
|
115
|
+
require(File.join(const_get('BASE_PATH'), path))
|
116
|
+
end
|
117
|
+
end
|
118
|
+
|
119
|
+
def initialize(app, options = {})
|
120
|
+
@app = app
|
121
|
+
@root = options[:root] || Utopia::Middleware::default_root
|
122
|
+
|
123
|
+
LOG.info "#{self.class.name}: Running in #{@root}"
|
124
|
+
|
125
|
+
@controllers = {}
|
126
|
+
@cache_controllers = true
|
127
|
+
|
128
|
+
if options[:controller_file]
|
129
|
+
@controller_file = options[:controller_file]
|
130
|
+
else
|
131
|
+
@controller_file = "controller.rb"
|
132
|
+
end
|
133
|
+
end
|
134
|
+
|
135
|
+
attr :app
|
136
|
+
|
137
|
+
def lookup(path)
|
138
|
+
if @cache_controllers
|
139
|
+
return @controllers.fetch(path.to_s) do |key|
|
140
|
+
@controllers[key] = load_file(path)
|
141
|
+
end
|
142
|
+
else
|
143
|
+
return load_file(path)
|
144
|
+
end
|
145
|
+
end
|
146
|
+
|
147
|
+
def load_file(path)
|
148
|
+
if path.directory?
|
149
|
+
base_path = File.join(@root, path.components)
|
150
|
+
else
|
151
|
+
base_path = File.join(@root, path.dirname.components)
|
152
|
+
end
|
153
|
+
|
154
|
+
controller_path = File.join(base_path, CONTROLLER_RB)
|
155
|
+
|
156
|
+
if File.exist?(controller_path)
|
157
|
+
klass = Class.new(Base)
|
158
|
+
klass.const_set('BASE_PATH', base_path)
|
159
|
+
|
160
|
+
$LOAD_PATH.unshift(base_path)
|
161
|
+
|
162
|
+
klass.class_eval(File.read(controller_path), controller_path)
|
163
|
+
|
164
|
+
$LOAD_PATH.delete(base_path)
|
165
|
+
|
166
|
+
return klass.new(self)
|
167
|
+
else
|
168
|
+
return nil
|
169
|
+
end
|
170
|
+
end
|
171
|
+
|
172
|
+
def fetch_controllers(path)
|
173
|
+
controllers = []
|
174
|
+
path.ascend do |parent_path|
|
175
|
+
controllers << lookup(parent_path)
|
176
|
+
end
|
177
|
+
|
178
|
+
return controllers.compact.reverse
|
179
|
+
end
|
180
|
+
|
181
|
+
def call(env)
|
182
|
+
env["utopia.controller"] ||= Variables.new
|
183
|
+
|
184
|
+
request = Rack::Request.new(env)
|
185
|
+
|
186
|
+
path = Path.create(request.path_info)
|
187
|
+
fetch_controllers(path).each do |controller|
|
188
|
+
if result = controller.process!(path, request)
|
189
|
+
return result
|
190
|
+
end
|
191
|
+
end
|
192
|
+
|
193
|
+
return @app.call(env)
|
194
|
+
end
|
195
|
+
end
|
196
|
+
|
197
|
+
end
|
198
|
+
end
|