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.
Files changed (37) hide show
  1. data/ext/utopia/xnode/fast_scanner/extconf.rb +6 -0
  2. data/ext/utopia/xnode/fast_scanner/parser.c +289 -0
  3. data/lib/utopia.rb +2 -0
  4. data/lib/utopia/etanni.rb +96 -0
  5. data/lib/utopia/extensions.rb +87 -0
  6. data/lib/utopia/link.rb +243 -0
  7. data/lib/utopia/middleware.rb +33 -0
  8. data/lib/utopia/middleware/all.rb +24 -0
  9. data/lib/utopia/middleware/benchmark.rb +47 -0
  10. data/lib/utopia/middleware/content.rb +139 -0
  11. data/lib/utopia/middleware/content/node.rb +363 -0
  12. data/lib/utopia/middleware/controller.rb +198 -0
  13. data/lib/utopia/middleware/directory_index.rb +54 -0
  14. data/lib/utopia/middleware/localization.rb +94 -0
  15. data/lib/utopia/middleware/localization/name.rb +64 -0
  16. data/lib/utopia/middleware/logger.rb +68 -0
  17. data/lib/utopia/middleware/redirector.rb +171 -0
  18. data/lib/utopia/middleware/requester.rb +116 -0
  19. data/lib/utopia/middleware/static.rb +186 -0
  20. data/lib/utopia/path.rb +193 -0
  21. data/lib/utopia/response_helper.rb +22 -0
  22. data/lib/utopia/session/encrypted_cookie.rb +115 -0
  23. data/lib/utopia/tag.rb +84 -0
  24. data/lib/utopia/tags.rb +32 -0
  25. data/lib/utopia/tags/all.rb +25 -0
  26. data/lib/utopia/tags/env.rb +24 -0
  27. data/lib/utopia/tags/fortune.rb +20 -0
  28. data/lib/utopia/tags/gallery.rb +175 -0
  29. data/lib/utopia/tags/google_analytics.rb +37 -0
  30. data/lib/utopia/tags/node.rb +24 -0
  31. data/lib/utopia/tags/override.rb +28 -0
  32. data/lib/utopia/time_store.rb +102 -0
  33. data/lib/utopia/version.rb +24 -0
  34. data/lib/utopia/xnode.rb +17 -0
  35. data/lib/utopia/xnode/processor.rb +97 -0
  36. data/lib/utopia/xnode/scanner.rb +153 -0
  37. metadata +168 -0
@@ -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