utopia 0.9.17

Sign up to get free protection for your applications and to get access to all the features.
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