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
data/lib/utopia/link.rb
ADDED
@@ -0,0 +1,243 @@
|
|
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/extensions'
|
17
|
+
|
18
|
+
module Utopia
|
19
|
+
|
20
|
+
class Link
|
21
|
+
XNODE_EXT = ".xnode"
|
22
|
+
|
23
|
+
def initialize(kind, path, info = nil)
|
24
|
+
@kind = kind
|
25
|
+
|
26
|
+
case @kind
|
27
|
+
when :file
|
28
|
+
@name = path.basename(XNODE_EXT)
|
29
|
+
@path = path
|
30
|
+
when :directory
|
31
|
+
@name = path.dirname.basename(XNODE_EXT)
|
32
|
+
@path = path
|
33
|
+
when :virtual
|
34
|
+
@name = path.to_s
|
35
|
+
@path = nil
|
36
|
+
end
|
37
|
+
|
38
|
+
@components = @name.split(".")
|
39
|
+
@locale = components[1..-1].join(".")
|
40
|
+
@title = components[0]
|
41
|
+
|
42
|
+
if info
|
43
|
+
@info = info.symbolize_keys
|
44
|
+
else
|
45
|
+
@info = {}
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
attr :kind
|
50
|
+
attr :name
|
51
|
+
attr :path
|
52
|
+
attr :locale
|
53
|
+
attr :info
|
54
|
+
attr :components
|
55
|
+
|
56
|
+
def [] (key)
|
57
|
+
if key == :title
|
58
|
+
return @title
|
59
|
+
end
|
60
|
+
|
61
|
+
return @info[key]
|
62
|
+
end
|
63
|
+
|
64
|
+
def href
|
65
|
+
if @info[:uri]
|
66
|
+
return @info[:uri]
|
67
|
+
elsif @path
|
68
|
+
return @path.to_s
|
69
|
+
else
|
70
|
+
"\#"
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
def href?
|
75
|
+
return href != "\#"
|
76
|
+
end
|
77
|
+
|
78
|
+
def title
|
79
|
+
@info[:title] || @title.to_title
|
80
|
+
end
|
81
|
+
|
82
|
+
def external?
|
83
|
+
@info.key? :uri
|
84
|
+
end
|
85
|
+
|
86
|
+
def to_href(options = {})
|
87
|
+
options[:content] ||= title
|
88
|
+
options[:class] ||= "link"
|
89
|
+
|
90
|
+
if href == "\#"
|
91
|
+
"<span class=#{options[:class].dump}>#{options[:content].to_html}</span>"
|
92
|
+
else
|
93
|
+
"<a class=#{options[:class].dump} href=\"#{href.to_html}\">#{options[:content].to_html}</a>"
|
94
|
+
end
|
95
|
+
end
|
96
|
+
|
97
|
+
def eql? other
|
98
|
+
if other && self.class == other.class
|
99
|
+
return kind.eql?(other.kind) &&
|
100
|
+
name.eql?(other.name) &&
|
101
|
+
path.eql?(other.path) &&
|
102
|
+
info.eql?(other.info)
|
103
|
+
else
|
104
|
+
return false
|
105
|
+
end
|
106
|
+
end
|
107
|
+
|
108
|
+
def == other
|
109
|
+
return other && kind == other.kind && name == other.name && path == other.path
|
110
|
+
end
|
111
|
+
end
|
112
|
+
|
113
|
+
module Links
|
114
|
+
XNODE_FILTER = /^(.+)\.xnode$/
|
115
|
+
INDEX_XNODE_FILTER = /^(index(\..+)*)\.xnode$/
|
116
|
+
LINKS_YAML = "links.yaml"
|
117
|
+
|
118
|
+
def self.metadata(path)
|
119
|
+
links_path = File.join(path, LINKS_YAML)
|
120
|
+
if File.exist?(links_path)
|
121
|
+
return YAML::load(File.read(links_path))
|
122
|
+
else
|
123
|
+
return {}
|
124
|
+
end
|
125
|
+
end
|
126
|
+
|
127
|
+
def self.indices(path, &block)
|
128
|
+
entries = Dir.entries(path).delete_if{|filename| !filename.match(INDEX_XNODE_FILTER)}
|
129
|
+
|
130
|
+
if block_given?
|
131
|
+
entries.each &block
|
132
|
+
else
|
133
|
+
return entries
|
134
|
+
end
|
135
|
+
end
|
136
|
+
|
137
|
+
DEFAULT_OPTIONS = {
|
138
|
+
:directories => true,
|
139
|
+
:files => true,
|
140
|
+
:virtual => true,
|
141
|
+
:indices => false,
|
142
|
+
:sort => :order,
|
143
|
+
:hidden => :hidden,
|
144
|
+
:locale => nil
|
145
|
+
}
|
146
|
+
|
147
|
+
def self.index(root, top, options = {})
|
148
|
+
options = DEFAULT_OPTIONS.merge(options)
|
149
|
+
path = File.join(root, top.components)
|
150
|
+
metadata = Links.metadata(path)
|
151
|
+
|
152
|
+
links = []
|
153
|
+
|
154
|
+
Dir.entries(path).each do |filename|
|
155
|
+
next if filename.match(/^[\._]/)
|
156
|
+
|
157
|
+
fullpath = File.join(path, filename)
|
158
|
+
|
159
|
+
if File.directory?(fullpath) && options[:directories]
|
160
|
+
name = filename
|
161
|
+
indices_metadata = Links.metadata(fullpath)
|
162
|
+
|
163
|
+
directory_metadata = metadata.delete(name) || {}
|
164
|
+
indices = 0
|
165
|
+
Links.indices(fullpath) do |index|
|
166
|
+
index_name = File.basename(index, ".xnode")
|
167
|
+
index_metadata = directory_metadata.merge(indices_metadata[index_name] || {})
|
168
|
+
|
169
|
+
links << Link.new(:directory, top + [filename, index_name], index_metadata)
|
170
|
+
indices += 1
|
171
|
+
end
|
172
|
+
|
173
|
+
if indices == 0
|
174
|
+
links << Link.new(:directory, top + [filename, ""], directory_metadata.merge(:uri => "\#"))
|
175
|
+
end
|
176
|
+
elsif filename.match(INDEX_XNODE_FILTER) && options[:indices] == false
|
177
|
+
name = $1
|
178
|
+
metadata.delete(name)
|
179
|
+
|
180
|
+
# We don't include indices in the list of pages.
|
181
|
+
next
|
182
|
+
elsif filename.match(XNODE_FILTER) && options[:files]
|
183
|
+
name = $1
|
184
|
+
|
185
|
+
links << Link.new(:file, top + name, metadata.delete(name))
|
186
|
+
end
|
187
|
+
end
|
188
|
+
|
189
|
+
if options[:virtual]
|
190
|
+
metadata.each do |name, details|
|
191
|
+
links << Link.new(:virtual, name, details)
|
192
|
+
end
|
193
|
+
end
|
194
|
+
|
195
|
+
if options[:hidden]
|
196
|
+
links = links.delete_if{|link| link[options[:hidden]]}
|
197
|
+
end
|
198
|
+
|
199
|
+
if options[:name]
|
200
|
+
case options[:name]
|
201
|
+
when Regexp
|
202
|
+
links.reject!{|link| !link.name.match(options[:name])}
|
203
|
+
when String
|
204
|
+
links.reject!{|link| link.name.index(options[:name]) != 0}
|
205
|
+
end
|
206
|
+
end
|
207
|
+
|
208
|
+
if options[:locale]
|
209
|
+
reduced = []
|
210
|
+
|
211
|
+
links.group_by(&:name).each do |name, links|
|
212
|
+
default = nil
|
213
|
+
|
214
|
+
link = links.reject{|link|
|
215
|
+
!(link.locale == options[:locale] || link.locale == "")
|
216
|
+
}.sort_by{|link| link.locale.size}.last
|
217
|
+
|
218
|
+
if link
|
219
|
+
reduced << link
|
220
|
+
end
|
221
|
+
end
|
222
|
+
|
223
|
+
links = reduced
|
224
|
+
end
|
225
|
+
|
226
|
+
if options[:sort]
|
227
|
+
links = links.sort do |a, b|
|
228
|
+
result = nil
|
229
|
+
begin
|
230
|
+
result ||= (a[options[:sort]] <=> b[options[:sort]])
|
231
|
+
rescue
|
232
|
+
# LOG.debug("Invalid comparison between #{a.path} and #{b.path} using key #{options[:sort]}!")
|
233
|
+
end
|
234
|
+
|
235
|
+
result ||= (a.title <=> b.title)
|
236
|
+
end
|
237
|
+
end
|
238
|
+
|
239
|
+
return links
|
240
|
+
end
|
241
|
+
end
|
242
|
+
|
243
|
+
end
|
@@ -0,0 +1,33 @@
|
|
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 'pathname'
|
17
|
+
require 'logger'
|
18
|
+
|
19
|
+
module Utopia
|
20
|
+
LOG = Logger.new($stderr)
|
21
|
+
LOG.level = Logger::DEBUG
|
22
|
+
|
23
|
+
module Middleware
|
24
|
+
def self.default_root(subdir = "pages")
|
25
|
+
Pathname.new(Dir.pwd).join(subdir).realpath.to_s
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
require 'utopia/extensions'
|
31
|
+
require 'utopia/path'
|
32
|
+
require 'utopia/tag'
|
33
|
+
|
@@ -0,0 +1,24 @@
|
|
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 'pathname'
|
17
|
+
|
18
|
+
Pathname.new(__FILE__).dirname.entries.grep(/\.rb$/).each do |path|
|
19
|
+
name = File.basename(path.to_s, ".rb")
|
20
|
+
|
21
|
+
if name != "all"
|
22
|
+
require "utopia/middleware/#{name}"
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,47 @@
|
|
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
|
+
|
18
|
+
module Utopia
|
19
|
+
module Middleware
|
20
|
+
|
21
|
+
class Benchmark
|
22
|
+
def initialize(app, options = {})
|
23
|
+
@app = app
|
24
|
+
@tag = options[:tag] || "{{benchmark}}"
|
25
|
+
end
|
26
|
+
|
27
|
+
def call(env)
|
28
|
+
start = Time.now
|
29
|
+
response = @app.call(env)
|
30
|
+
finish = Time.now
|
31
|
+
|
32
|
+
time = "%0.4f" % (finish - start)
|
33
|
+
# LOG.debug "benchmark: Request #{env["PATH_INFO"]} took #{time}s"
|
34
|
+
buf = StringIO.new
|
35
|
+
|
36
|
+
response[2].each do |text|
|
37
|
+
buf.write(text.gsub(@tag, time))
|
38
|
+
end
|
39
|
+
|
40
|
+
buf.rewind
|
41
|
+
|
42
|
+
[response[0], response[1], buf]
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
end
|
47
|
+
end
|
@@ -0,0 +1,139 @@
|
|
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/link'
|
18
|
+
require 'utopia/path'
|
19
|
+
require 'utopia/tags'
|
20
|
+
|
21
|
+
require 'utopia/middleware/content/node'
|
22
|
+
require 'utopia/etanni'
|
23
|
+
|
24
|
+
module Utopia
|
25
|
+
module Middleware
|
26
|
+
|
27
|
+
class Content
|
28
|
+
def initialize(app, options = {})
|
29
|
+
@app = app
|
30
|
+
|
31
|
+
@root = File.expand_path(options[:root] || Utopia::Middleware::default_root)
|
32
|
+
|
33
|
+
LOG.info "#{self.class.name}: Running in #{@root}"
|
34
|
+
|
35
|
+
# Set to hash to enable caching
|
36
|
+
@nodes = {}
|
37
|
+
@files = nil
|
38
|
+
|
39
|
+
@tags = options[:tags] || {}
|
40
|
+
end
|
41
|
+
|
42
|
+
attr :root
|
43
|
+
attr :passthrough
|
44
|
+
|
45
|
+
def fetch_xml(path)
|
46
|
+
read_file = lambda { TemplateCache.new(path, Etanni) }
|
47
|
+
|
48
|
+
if @files
|
49
|
+
@files.fetch(path) do
|
50
|
+
@files[path] = read_file.call
|
51
|
+
end
|
52
|
+
else
|
53
|
+
read_file.call
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
# Look up a named tag such as <entry />
|
58
|
+
def lookup_tag(name, parent_path)
|
59
|
+
if @tags.key? name
|
60
|
+
return @tags[name]
|
61
|
+
elsif Utopia::Tags.all.key? name
|
62
|
+
return Utopia::Tags.all[name]
|
63
|
+
end
|
64
|
+
|
65
|
+
if String === name && name.index("/")
|
66
|
+
name = Path.create(name)
|
67
|
+
end
|
68
|
+
|
69
|
+
if Path === name
|
70
|
+
name = parent_path + name
|
71
|
+
name_path = name.components.dup
|
72
|
+
name_path[-1] += ".xnode"
|
73
|
+
else
|
74
|
+
name_path = name + ".xnode"
|
75
|
+
end
|
76
|
+
|
77
|
+
parent_path.ascend do |dir|
|
78
|
+
tag_path = File.join(root, dir.components, name_path)
|
79
|
+
|
80
|
+
if File.exist? tag_path
|
81
|
+
return Node.new(self, dir + name, parent_path + name, tag_path)
|
82
|
+
end
|
83
|
+
|
84
|
+
if String === name_path
|
85
|
+
tag_path = File.join(root, dir.components, "_" + name_path)
|
86
|
+
|
87
|
+
if File.exist? tag_path
|
88
|
+
return Node.new(self, dir + name, parent_path + name, tag_path)
|
89
|
+
end
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
93
|
+
return nil
|
94
|
+
end
|
95
|
+
|
96
|
+
def lookup_node(request_path)
|
97
|
+
name = request_path.basename
|
98
|
+
name_xnode = name + ".xnode"
|
99
|
+
|
100
|
+
node_path = File.join(@root, request_path.dirname.components, name_xnode)
|
101
|
+
|
102
|
+
if File.exist? node_path
|
103
|
+
return Node.new(self, request_path.dirname + name, request_path, node_path)
|
104
|
+
end
|
105
|
+
|
106
|
+
return nil
|
107
|
+
end
|
108
|
+
|
109
|
+
def call(env)
|
110
|
+
request = Rack::Request.new(env)
|
111
|
+
path = Path.create(request.path_info).to_absolute
|
112
|
+
|
113
|
+
# Check if the request is to a non-specific index.
|
114
|
+
name, extensions = path.basename.split(".", 2)
|
115
|
+
directory_path = File.join(@root, path.dirname.components, name)
|
116
|
+
|
117
|
+
if File.directory? directory_path
|
118
|
+
return [307, {"Location" => path.dirname.join([name, "index.#{extensions}"]).to_s}, []]
|
119
|
+
end
|
120
|
+
|
121
|
+
# Otherwise look up the node
|
122
|
+
node = lookup_node(path)
|
123
|
+
|
124
|
+
if node
|
125
|
+
if request.head?
|
126
|
+
return [200, {}, []]
|
127
|
+
else
|
128
|
+
response = Rack::Response.new
|
129
|
+
node.process!(request, response)
|
130
|
+
return response.finish
|
131
|
+
end
|
132
|
+
else
|
133
|
+
return @app.call(env)
|
134
|
+
end
|
135
|
+
end
|
136
|
+
end
|
137
|
+
|
138
|
+
end
|
139
|
+
end
|