nitro 0.31.0 → 0.40.0
Sign up to get free protection for your applications and to get access to all the features.
- data/bin/nitro +135 -37
- data/doc/CHANGELOG.1 +108 -108
- data/doc/CHANGELOG.2 +89 -89
- data/doc/CHANGELOG.3 +105 -105
- data/{CHANGELOG → doc/CHANGELOG.4} +509 -509
- data/doc/{AUTHORS → CONTRIBUTORS} +49 -37
- data/doc/LIBRARIES +13 -0
- data/doc/LICENSE +2 -3
- data/doc/MIGRATION +45 -0
- data/doc/RELEASES +131 -11
- data/doc/TODO +67 -0
- data/lib/glue/magick.rb +0 -3
- data/lib/glue/sweeper.rb +30 -15
- data/lib/glue/thumbnails.rb +0 -2
- data/lib/glue/webfile.rb +23 -11
- data/lib/nitro.rb +37 -44
- data/lib/nitro/adapter/cgi.rb +0 -3
- data/lib/nitro/adapter/console.rb +0 -2
- data/lib/nitro/adapter/fastcgi.rb +6 -3
- data/lib/nitro/adapter/mongrel.rb +97 -58
- data/lib/nitro/adapter/script.rb +4 -6
- data/lib/nitro/adapter/webrick.rb +33 -87
- data/lib/nitro/adapter/webrick/vcr.rb +85 -0
- data/lib/nitro/caching.rb +0 -2
- data/lib/nitro/caching/actions.rb +0 -2
- data/lib/nitro/caching/fragments.rb +0 -2
- data/lib/nitro/caching/output.rb +45 -16
- data/lib/nitro/caching/proxy.rb +49 -0
- data/lib/nitro/cgi.rb +3 -6
- data/lib/nitro/cgi/cookie.rb +0 -3
- data/lib/nitro/cgi/request.rb +67 -24
- data/lib/nitro/cgi/response.rb +0 -2
- data/lib/nitro/cgi/{sendfile.rb → send_file.rb} +7 -6
- data/lib/nitro/compiler.rb +62 -55
- data/lib/nitro/compiler/cleanup.rb +0 -3
- data/lib/nitro/compiler/elements.rb +31 -28
- data/lib/nitro/compiler/errors.rb +2 -5
- data/lib/nitro/compiler/include.rb +10 -8
- data/lib/nitro/compiler/layout.rb +0 -2
- data/lib/nitro/compiler/localization.rb +0 -2
- data/lib/nitro/compiler/markup.rb +14 -6
- data/lib/nitro/compiler/morphing.rb +1 -5
- data/lib/nitro/compiler/script.rb +2 -4
- data/lib/nitro/compiler/squeeze.rb +0 -2
- data/lib/nitro/compiler/xslt.rb +0 -2
- data/lib/nitro/context.rb +10 -5
- data/lib/nitro/control.rb +18 -0
- data/lib/nitro/control/attribute.rb +88 -0
- data/lib/nitro/control/attribute/checkbox.rb +19 -0
- data/lib/nitro/control/attribute/datetime.rb +21 -0
- data/lib/nitro/control/attribute/file.rb +20 -0
- data/lib/nitro/control/attribute/fixnum.rb +26 -0
- data/lib/nitro/control/attribute/float.rb +26 -0
- data/lib/nitro/control/attribute/options.rb +38 -0
- data/lib/nitro/control/attribute/password.rb +16 -0
- data/lib/nitro/control/attribute/text.rb +16 -0
- data/lib/nitro/control/attribute/textarea.rb +16 -0
- data/lib/nitro/control/none.rb +16 -0
- data/lib/nitro/control/relation.rb +53 -0
- data/lib/nitro/control/relation/belongs_to.rb +0 -0
- data/lib/nitro/control/relation/has_many.rb +97 -0
- data/lib/nitro/control/relation/joins_many.rb +0 -0
- data/lib/nitro/control/relation/many_to_many.rb +0 -0
- data/lib/nitro/control/relation/refers_to.rb +29 -0
- data/lib/nitro/controller.rb +7 -296
- data/lib/nitro/dispatcher.rb +72 -34
- data/lib/nitro/element.rb +36 -10
- data/lib/nitro/element/javascript.rb +0 -2
- data/lib/nitro/flash.rb +23 -10
- data/lib/nitro/global.rb +36 -11
- data/lib/nitro/helper.rb +22 -8
- data/lib/nitro/helper/benchmark.rb +0 -2
- data/lib/nitro/helper/buffer.rb +0 -3
- data/lib/nitro/helper/css.rb +12 -0
- data/lib/nitro/helper/debug.rb +1 -3
- data/lib/nitro/helper/default.rb +1 -0
- data/lib/nitro/helper/feed.rb +400 -386
- data/lib/nitro/helper/form.rb +246 -116
- data/lib/nitro/helper/javascript.rb +28 -2
- data/lib/nitro/helper/javascript/morphing.rb +0 -2
- data/lib/nitro/helper/javascript/prototype.rb +0 -2
- data/lib/nitro/helper/javascript/scriptaculous.rb +0 -1
- data/lib/nitro/helper/layout.rb +0 -2
- data/lib/nitro/helper/navigation.rb +87 -0
- data/lib/nitro/helper/pager.rb +11 -22
- data/lib/nitro/helper/table.rb +9 -32
- data/lib/nitro/helper/url.rb +104 -0
- data/lib/nitro/helper/xhtml.rb +20 -4
- data/lib/nitro/helper/xml.rb +0 -2
- data/lib/nitro/markup.rb +131 -0
- data/lib/nitro/part.rb +52 -7
- data/lib/nitro/publishable.rb +328 -0
- data/lib/nitro/render.rb +30 -61
- data/lib/nitro/router.rb +12 -4
- data/lib/nitro/sanitize.rb +48 -0
- data/lib/nitro/scaffold.rb +9 -11
- data/lib/nitro/scaffold/controller.rb +25 -0
- data/lib/nitro/scaffold/model.rb +150 -0
- data/lib/nitro/scaffolding.rb +1 -3
- data/lib/nitro/server.rb +57 -32
- data/lib/nitro/server/drb.rb +16 -2
- data/lib/nitro/server/runner.rb +80 -102
- data/lib/nitro/service.rb +0 -1
- data/lib/nitro/service/xmlrpc.rb +0 -2
- data/lib/nitro/session.rb +26 -18
- data/lib/nitro/session/drb.rb +2 -16
- data/lib/nitro/session/memory.rb +0 -2
- data/lib/nitro/template.rb +219 -0
- data/lib/nitro/test/assertions.rb +1 -3
- data/lib/nitro/test/context.rb +0 -1
- data/lib/nitro/test/testcase.rb +0 -1
- data/lib/nitro/version.rb +6 -0
- data/lib/part/admin.rb +16 -0
- data/lib/part/admin/controller.rb +19 -0
- data/lib/part/admin/helper.rb +30 -0
- data/lib/part/admin/og/controller.rb +114 -0
- data/lib/part/admin/og/customize.rb +4 -0
- data/lib/part/admin/og/template/index.xhtml +27 -0
- data/lib/part/admin/og/template/list.xhtml +38 -0
- data/lib/part/admin/og/template/search.xhtml +20 -0
- data/lib/part/admin/og/template/update.xhtml +25 -0
- data/lib/part/admin/skin.rb +207 -0
- data/lib/part/admin/template/denied.xhtml +13 -0
- data/lib/part/admin/template/index.xhtml +12 -0
- data/lib/part/admin/todo.txt +2 -0
- data/proto/public/error.xhtml +4 -2
- data/proto/run.rb +0 -2
- data/test/glue/tc_webfile.rb +1 -0
- data/test/nitro/cgi/tc_request.rb +23 -0
- data/test/nitro/helper/tc_feed.rb +0 -3
- data/test/nitro/helper/tc_navbar.rb +74 -0
- data/test/nitro/helper/tc_table.rb +2 -0
- data/test/nitro/tc_cgi.rb +72 -19
- data/test/nitro/tc_controller.rb +35 -26
- data/test/nitro/tc_controller_aspect.rb +1 -0
- data/test/nitro/tc_controller_params.rb +864 -0
- data/test/nitro/tc_dispatcher.rb +2 -2
- data/test/nitro/tc_element.rb +16 -16
- data/test/nitro/tc_flash.rb +3 -3
- data/test/nitro/tc_markup.rb +31 -0
- data/test/nitro/tc_render.rb +12 -14
- data/test/nitro/tc_session.rb +9 -7
- data/test/nitro/tc_template.rb +34 -0
- metadata +217 -198
- data/INSTALL +0 -121
- data/ProjectInfo +0 -74
- data/README +0 -555
- data/doc/apache.txt +0 -9
- data/doc/config.txt +0 -28
- data/doc/faq.txt +0 -7
- data/doc/lhttpd.txt +0 -7
- data/lib/nitro/adapter/scgi.rb +0 -239
- data/lib/nitro/helper/form/builder.rb +0 -144
- data/lib/nitro/helper/form/controls.rb +0 -389
- data/lib/nitro/helper/rss.rb +0 -72
- data/proto/conf/apache.conf +0 -51
- data/proto/public/scaffold/advanced_search.xhtml +0 -30
- data/proto/public/scaffold/edit.xhtml +0 -11
- data/proto/public/scaffold/form.xhtml +0 -1
- data/proto/public/scaffold/index.xhtml +0 -20
- data/proto/public/scaffold/list.xhtml +0 -32
- data/proto/public/scaffold/new.xhtml +0 -11
- data/proto/public/scaffold/search.xhtml +0 -29
- data/proto/public/scaffold/view.xhtml +0 -8
- data/proto/script/scgi_ctl +0 -221
- data/proto/script/scgi_service +0 -128
- data/setup.rb +0 -1585
- data/src/part/admin.rb +0 -16
- data/src/part/admin/controller.rb +0 -81
- data/src/part/admin/skin.rb +0 -21
- data/src/part/admin/system.css +0 -135
- data/src/part/admin/template/denied.xhtml +0 -1
- data/src/part/admin/template/index.xhtml +0 -43
- data/test/nitro/helper/tc_rss.rb +0 -24
data/lib/nitro/helper/buffer.rb
CHANGED
data/lib/nitro/helper/debug.rb
CHANGED
@@ -16,7 +16,7 @@ private
|
|
16
16
|
begin
|
17
17
|
Marshal::dump(object)
|
18
18
|
"<pre class='debug_dump'>#{object.to_yaml.gsub(" ", " ")}</pre>"
|
19
|
-
rescue
|
19
|
+
rescue TypeError => ex
|
20
20
|
# Object couldn't be dumped, perhaps because of singleton
|
21
21
|
# methods, this is the fallback.
|
22
22
|
"<code class='debug_dump'>#{object.inspect}</code>"
|
@@ -26,5 +26,3 @@ private
|
|
26
26
|
end
|
27
27
|
|
28
28
|
end
|
29
|
-
|
30
|
-
# * George Moschovitis <gm@navel.gr>
|
data/lib/nitro/helper/default.rb
CHANGED
data/lib/nitro/helper/feed.rb
CHANGED
@@ -1,5 +1,5 @@
|
|
1
1
|
require 'rss/maker'
|
2
|
-
require '
|
2
|
+
require 'nitro/markup'
|
3
3
|
require 'rexml/document'
|
4
4
|
require 'time'
|
5
5
|
require 'uri'
|
@@ -8,425 +8,439 @@ require 'facet/string/first_char'
|
|
8
8
|
|
9
9
|
module Nitro
|
10
10
|
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
module FeedHelper
|
102
|
-
include Glue::Markup
|
103
|
-
|
104
|
-
# RSS 0.91, 1.0, 2.0 feeds.
|
105
|
-
|
106
|
-
def build_rss(objects, options = {})
|
11
|
+
# A helper that provides Feed related methods.
|
12
|
+
#
|
13
|
+
# To include this helper into your controller,
|
14
|
+
# add the following at the beginning of your controller:
|
15
|
+
#
|
16
|
+
# helper :feed
|
17
|
+
#
|
18
|
+
# Then define actions that set an appropriate content_type and use build_(rss|atom|opml)
|
19
|
+
# to generate your desired feed. See below for details.
|
20
|
+
#
|
21
|
+
# == RSS 0.91, 1.0 and 2.0
|
22
|
+
#
|
23
|
+
# response.content_type = "application/rss+xml"
|
24
|
+
# build_rss(og_objects,
|
25
|
+
# :version => "0.9",
|
26
|
+
# :base => context.host_url, # + object.to_href results in an item-link
|
27
|
+
# :link => context.host_url+"/feed", # link to this feed
|
28
|
+
# :title => "Feed Title",
|
29
|
+
# :description => "What this feed is about",
|
30
|
+
# :search_title => "Search Form Here",
|
31
|
+
# :search_description => "Search description",
|
32
|
+
# :search_input_name => "search_field",
|
33
|
+
# :search_form_action => "http://url/to/search_action"
|
34
|
+
# )
|
35
|
+
#
|
36
|
+
# For RSS 1.0 or RSS 2.0 just change :version (defaults to '0.91'),
|
37
|
+
# possible :version options are "0.9", "0.91", "1.0" and "2.0"
|
38
|
+
#
|
39
|
+
# * for RSS 0.9 :language is required (or defaults to 'en')
|
40
|
+
# * for all RSS versions :title, :link and/or :base, :description are required
|
41
|
+
#
|
42
|
+
# <b>individual objects have to respond to at least:</b>
|
43
|
+
#
|
44
|
+
# * 1.0/0.9/2.0 require @title
|
45
|
+
# * 1.0/0.9 require @to_href
|
46
|
+
# * 2.0 requires @body
|
47
|
+
#
|
48
|
+
# if it doesn't, no item is created
|
49
|
+
#
|
50
|
+
# * @update_time, @create_time or @date is used for item.date
|
51
|
+
# * so if Og's "is Timestamped" is being used, it'll be @update_time
|
52
|
+
# * @author.name can optionally be used for item.author
|
53
|
+
#
|
54
|
+
# == Atom 1.0
|
55
|
+
#
|
56
|
+
# response.content_type = "application/atom+xml"
|
57
|
+
# build_atom(og_objects,
|
58
|
+
# :title => "Feed Title",
|
59
|
+
# :base => context.host_url, # + object.to_href results in an item-link
|
60
|
+
# :link => context.host_url+"/atomfeed",
|
61
|
+
# :id => "your_unique_id", # :base is being used unless :id specified (:base is recommended)
|
62
|
+
# :author_name => "Takeo",
|
63
|
+
# :author_email => "email@example.com",
|
64
|
+
# :author_link => "http://uri.to/authors/home",
|
65
|
+
# )
|
66
|
+
#
|
67
|
+
# <b>individual objects have to respond to at least:</b>
|
68
|
+
#
|
69
|
+
# * @title
|
70
|
+
# * @to_href
|
71
|
+
# * @update_time/@create_time/@date (at least one of them)
|
72
|
+
#
|
73
|
+
# if it doesn't, no entry is created
|
74
|
+
#
|
75
|
+
# optional:
|
76
|
+
#
|
77
|
+
# * @body (taken as summary (256 chars))
|
78
|
+
# * @full_content
|
79
|
+
# * use Og's "is Timestamped", so both @update_time and @create_time can be used
|
80
|
+
# * @author.name
|
81
|
+
# * @author.link
|
82
|
+
# * @author.email # be careful, you don't want to publish your users email address to spammers
|
83
|
+
#
|
84
|
+
#
|
85
|
+
# == OPML 1.0 feed lists
|
86
|
+
#
|
87
|
+
# Fabian: Eew, who invented OPML? Who needs it? Implementing it in a very rough way anyway though.
|
88
|
+
# takes a Hash of Feeds and optional options
|
89
|
+
#
|
90
|
+
# response.content_type = "application/opml+xml"
|
91
|
+
# build_opml(
|
92
|
+
# {
|
93
|
+
# "http://oxyliquit.de/feed" => "rss",
|
94
|
+
# "http://oxyliquit.de/feed/questions" => "rss",
|
95
|
+
# "http://oxyliquit.de/feed/tips" => "rss",
|
96
|
+
# "http://oxyliquit.de/feed/tutorials" => "rss"
|
97
|
+
# },
|
98
|
+
# :title => "My feeds"
|
99
|
+
# )
|
107
100
|
|
108
|
-
|
109
|
-
|
110
|
-
:title => 'Syndication',
|
111
|
-
:description => 'Syndication',
|
112
|
-
:version => '0.9',
|
113
|
-
:language => 'en', # required by 0.9
|
114
|
-
}.update(options)
|
101
|
+
module FeedHelper
|
102
|
+
include Nitro::Markup
|
115
103
|
|
116
|
-
|
104
|
+
# RSS 0.91, 1.0, 2.0 feeds.
|
117
105
|
|
118
|
-
|
119
|
-
raise "Option ':base' cannot be omitted!" unless options[:base]
|
120
|
-
|
121
|
-
# build rss
|
122
|
-
rss = RSS::Maker.make(options[:version]) do |maker|
|
123
|
-
maker.channel.title = options[:title]
|
124
|
-
maker.channel.description = options[:description]
|
125
|
-
if options[:link]
|
126
|
-
maker.channel.link = options[:link]
|
127
|
-
else
|
128
|
-
maker.channel.link = options[:base] #FIXME: not sure
|
129
|
-
end
|
130
|
-
case options[:version]
|
131
|
-
when '0.9', '0.91'
|
132
|
-
maker.channel.language = options[:language]
|
133
|
-
when '1.0'
|
134
|
-
if options[:link]
|
135
|
-
maker.channel.about = options[:link]
|
136
|
-
else
|
137
|
-
raise "Option ':link' is required for RSS 1.0"
|
138
|
-
end
|
139
|
-
end
|
140
|
-
maker.channel.generator = "Nitro " + Nitro::Version.to_s
|
106
|
+
def build_rss(objects, options = {})
|
141
107
|
|
142
|
-
|
108
|
+
# default options
|
109
|
+
options = {
|
110
|
+
:title => 'Syndication',
|
111
|
+
:description => 'Syndication',
|
112
|
+
:version => '0.9',
|
113
|
+
:language => 'en', # required by 0.9
|
114
|
+
}.update(options)
|
143
115
|
|
144
|
-
|
145
|
-
# * 1.0/0.9/2.0 require @title
|
146
|
-
# * 1.0/0.9 require @link
|
147
|
-
# * 2.0 requires @description
|
148
|
-
objects.each do |o|
|
149
|
-
|
150
|
-
# new Item
|
151
|
-
item = maker.items.new_item
|
152
|
-
|
153
|
-
# Link
|
154
|
-
item.link = "#{options[:base]}/#{o.to_href}" if o.respond_to?(:to_href)
|
155
|
-
item.guid.content = "#{options[:base]}/#{o.to_href}" if options[:version] == '2.0' && o.respond_to?(:to_href)
|
156
|
-
|
157
|
-
# Title
|
158
|
-
item.title = o.title if o.respond_to?(:title)
|
159
|
-
|
160
|
-
# Description
|
161
|
-
if o.respond_to? :body and body = o.body
|
162
|
-
#TODO: think about whether markup should always be done
|
163
|
-
# and whether 256 chars should be a fixed limit
|
164
|
-
#item.description = markup(body.first_char(256))
|
165
|
-
# markup disabled, feedvalidator.org says "description should not contain HTML"
|
166
|
-
# so removing everything that looks like a tag
|
167
|
-
item.description = body.first_char(256).gsub!(/<[^>]+>/, ' ')
|
168
|
-
end
|
169
|
-
|
170
|
-
# Date (item.date asks for a Time object, so don't .to_s !)
|
171
|
-
if o.respond_to?(:update_time)
|
172
|
-
item.date = o.update_time
|
173
|
-
elsif o.respond_to?(:create_time)
|
174
|
-
item.date = o.create_time
|
175
|
-
elsif o.respond_to?(:date)
|
176
|
-
item.date = o.date
|
177
|
-
end
|
178
|
-
|
179
|
-
# Author
|
180
|
-
if o.respond_to?(:author)
|
181
|
-
if o.author[:name]
|
182
|
-
item.author = o.author[:name]
|
183
|
-
end
|
184
|
-
end
|
185
|
-
|
186
|
-
end if objects.size > 0 # objects/items
|
116
|
+
raise "Option ':version' contains a wrong version!" unless %w(0.9 0.91 1.0 2.0).include?(options[:version])
|
187
117
|
|
188
|
-
|
189
|
-
|
190
|
-
|
191
|
-
|
192
|
-
|
118
|
+
options[:base] ||= options[:link]
|
119
|
+
raise "Option ':base' cannot be omitted!" unless options[:base]
|
120
|
+
|
121
|
+
# build rss
|
122
|
+
rss = RSS::Maker.make(options[:version]) do |maker|
|
123
|
+
maker.channel.title = options[:title]
|
124
|
+
maker.channel.description = options[:description]
|
125
|
+
if options[:link]
|
126
|
+
maker.channel.link = options[:link]
|
127
|
+
else
|
128
|
+
maker.channel.link = options[:base] #FIXME: not sure
|
129
|
+
end
|
130
|
+
case options[:version]
|
131
|
+
when '0.9', '0.91'
|
132
|
+
maker.channel.language = options[:language]
|
133
|
+
when '1.0'
|
134
|
+
if options[:link]
|
135
|
+
maker.channel.about = options[:link]
|
136
|
+
else
|
137
|
+
raise "Option ':link' is required for RSS 1.0"
|
138
|
+
end
|
193
139
|
end
|
194
|
-
|
195
|
-
|
196
|
-
|
197
|
-
|
140
|
+
maker.channel.generator = "Nitro #{Nitro::Version}"
|
141
|
+
|
142
|
+
maker.items.do_sort = true
|
143
|
+
|
144
|
+
# items for each object
|
145
|
+
# * 1.0/0.9/2.0 require @title
|
146
|
+
# * 1.0/0.9 require @link
|
147
|
+
# * 2.0 requires @description
|
148
|
+
objects.each do |o|
|
149
|
+
|
150
|
+
# new Item
|
151
|
+
item = maker.items.new_item
|
152
|
+
|
153
|
+
# Link
|
154
|
+
item.link = "#{options[:base]}#{fhref o}" if o.respond_to?(:to_href)
|
155
|
+
item.guid.content = "#{options[:base]}#{fhref o}" if options[:version] == '2.0' && o.respond_to?(:to_href)
|
156
|
+
|
157
|
+
# Title
|
158
|
+
item.title = o.title if o.respond_to?(:title)
|
159
|
+
|
160
|
+
# Description
|
161
|
+
if o.respond_to? :body and body = o.body
|
162
|
+
#TODO: think about whether markup should always be done
|
163
|
+
# and whether 256 chars should be a fixed limit
|
164
|
+
#item.description = markup(body.first_char(256))
|
165
|
+
# markup disabled, feedvalidator.org says "description should not contain HTML"
|
166
|
+
# so removing everything that looks like a tag
|
167
|
+
item.description = body.first_char(256).gsub!(/<[^>]+>/, ' ')
|
168
|
+
end
|
169
|
+
|
170
|
+
# Date (item.date asks for a Time object, so don't .to_s !)
|
171
|
+
if o.respond_to?(:update_time)
|
172
|
+
item.date = o.update_time
|
173
|
+
elsif o.respond_to?(:create_time)
|
174
|
+
item.date = o.create_time
|
175
|
+
elsif o.respond_to?(:date)
|
176
|
+
item.date = o.date
|
177
|
+
end
|
178
|
+
|
179
|
+
# Author. Use .to_s to be more flexible.
|
180
|
+
|
181
|
+
if o.respond_to?(:author)
|
182
|
+
item.author = o.author.to_s
|
183
|
+
end
|
184
|
+
|
185
|
+
end if objects.size > 0 # objects/items
|
186
|
+
|
187
|
+
# search form
|
188
|
+
maker.textinput.title = options[:search_title] if options[:search_title]
|
189
|
+
maker.textinput.description = options[:search_description] if options[:search_description]
|
190
|
+
maker.textinput.name = options[:search_input_name] if options[:search_input_name]
|
191
|
+
maker.textinput.link = options[:search_form_action] if options[:search_form_action]
|
192
|
+
end
|
198
193
|
|
194
|
+
return rss.to_s
|
195
|
+
end
|
196
|
+
alias_method :rss, :build_rss
|
197
|
+
|
198
|
+
|
199
|
+
# Atom 1.0 feeds.
|
200
|
+
|
201
|
+
def build_atom(objects, options = {})
|
199
202
|
|
200
|
-
#
|
203
|
+
# default options
|
204
|
+
options = {
|
205
|
+
:title => 'Syndication',
|
206
|
+
}.update(options)
|
201
207
|
|
202
|
-
|
203
|
-
|
204
|
-
|
205
|
-
|
206
|
-
|
207
|
-
|
208
|
-
|
209
|
-
|
210
|
-
|
211
|
-
|
212
|
-
|
213
|
-
# new XML Document for Atom
|
214
|
-
atom = REXML::Document.new
|
215
|
-
atom << REXML::XMLDecl.new("1.0", "utf-8")
|
208
|
+
raise "first param must be a collection of objects!" unless objects.respond_to?(:to_ary)
|
209
|
+
raise "your object(s) have to respond to :update_time, :create_time or :date" unless objects[0].respond_to?(:update_time) or objects[0].respond_to?(:create_time) or objects[0].respond_to?(:date)
|
210
|
+
raise "Option ':base' cannot be omitted!" unless options[:base]
|
211
|
+
|
212
|
+
# new XML Document for Atom
|
213
|
+
atom = REXML::Document.new
|
214
|
+
atom << REXML::XMLDecl.new("1.0", "utf-8")
|
215
|
+
|
216
|
+
# Root element <feed />
|
217
|
+
feed = REXML::Element.new("feed").add_namespace("http://www.w3.org/2005/Atom")
|
216
218
|
|
217
|
-
#
|
218
|
-
feed = REXML::Element.new("feed").add_namespace("http://www.w3.org/2005/Atom")
|
219
|
+
# Required feed elements
|
219
220
|
|
220
|
-
|
221
|
-
|
222
|
-
|
223
|
-
|
224
|
-
|
225
|
-
|
226
|
-
|
227
|
-
|
228
|
-
|
229
|
-
|
230
|
-
|
231
|
-
|
232
|
-
|
233
|
-
|
234
|
-
|
235
|
-
|
236
|
-
|
237
|
-
|
238
|
-
elsif o.respond_to?(:date)
|
239
|
-
latest = o.date if o.date > latest
|
240
|
-
end
|
221
|
+
# id: Identifies the feed using a universally unique and permanent URI.
|
222
|
+
iduri = URI.parse(options[:id] || options[:base]).normalize.to_s
|
223
|
+
id = REXML::Element.new("id").add_text(iduri)
|
224
|
+
feed << id
|
225
|
+
|
226
|
+
# title: Contains a human readable title for the feed.
|
227
|
+
title = REXML::Element.new("title").add_text(options[:title])
|
228
|
+
feed << title
|
229
|
+
|
230
|
+
# updated: Indicates the last time the feed was modified in a significant way.
|
231
|
+
latest = Time.at(0) # a while back
|
232
|
+
objects.each do |o|
|
233
|
+
if o.respond_to?(:update_time)
|
234
|
+
latest = o.update_time if o.update_time > latest
|
235
|
+
elsif o.respond_to?(:create_time)
|
236
|
+
latest = o.create_time if o.create_time > latest
|
237
|
+
elsif o.respond_to?(:date)
|
238
|
+
latest = o.date if o.date > latest
|
241
239
|
end
|
242
|
-
|
243
|
-
|
244
|
-
|
245
|
-
|
246
|
-
|
247
|
-
|
248
|
-
|
249
|
-
|
250
|
-
|
251
|
-
|
240
|
+
end
|
241
|
+
updated = REXML::Element.new("updated").add_text(latest.iso8601)
|
242
|
+
feed << updated
|
243
|
+
|
244
|
+
# Recommended feed elements
|
245
|
+
|
246
|
+
# link: A feed should contain a link back to the feed itself.
|
247
|
+
if options[:link]
|
248
|
+
link = REXML::Element.new("link")
|
249
|
+
link.add_attributes({ "rel" => "self", "href" => options[:link] })
|
250
|
+
feed << link
|
251
|
+
end
|
252
|
+
|
253
|
+
# author: Names one author of the feed.
|
254
|
+
if options[:author_name] # name is required for author
|
255
|
+
author = REXML::Element.new("author")
|
256
|
+
author_name = REXML::Element.new("name").add_text(options[:author_name])
|
257
|
+
author << author_name
|
258
|
+
if options[:author_email]
|
259
|
+
author_email = REXML::Element.new("email").add_text(options[:author_email])
|
260
|
+
author << author_email
|
252
261
|
end
|
253
|
-
|
254
|
-
|
255
|
-
|
256
|
-
author = REXML::Element.new("author")
|
257
|
-
author_name = REXML::Element.new("name").add_text(options[:author_name])
|
258
|
-
author << author_name
|
259
|
-
if options[:author_email]
|
260
|
-
author_email = REXML::Element.new("email").add_text(options[:author_email])
|
261
|
-
author << author_email
|
262
|
-
end
|
263
|
-
if options[:author_link]
|
264
|
-
author_link = REXML::Element.new("uri").add_text(options[:author_link])
|
265
|
-
author << author_link
|
266
|
-
end
|
267
|
-
feed << author
|
262
|
+
if options[:author_link]
|
263
|
+
author_link = REXML::Element.new("uri").add_text(options[:author_link])
|
264
|
+
author << author_link
|
268
265
|
end
|
266
|
+
feed << author
|
267
|
+
end
|
268
|
+
|
269
|
+
# Optional feed elements
|
270
|
+
|
271
|
+
# category:
|
272
|
+
# contributor:
|
273
|
+
# generator: Identifies the software used to generate the feed.
|
274
|
+
generator = REXML::Element.new("generator")
|
275
|
+
generator.add_attributes({ "uri" => "http://www.nitroproject.org", "version" => Nitro::Version })
|
276
|
+
generator.add_text("Nitro")
|
277
|
+
feed << generator
|
278
|
+
# icon
|
279
|
+
# logo
|
280
|
+
# rights
|
281
|
+
# subtitle
|
282
|
+
|
283
|
+
# Entries
|
284
|
+
objects.each do |o|
|
269
285
|
|
270
|
-
#
|
286
|
+
# new Entry (called "item" in RSS)
|
287
|
+
unless o.respond_to?(:to_href) and o.respond_to?(:title)
|
288
|
+
next
|
289
|
+
end
|
290
|
+
entry = REXML::Element.new("entry")
|
291
|
+
|
292
|
+
# Required entry elements
|
271
293
|
|
272
|
-
|
273
|
-
|
274
|
-
|
275
|
-
|
276
|
-
|
277
|
-
generator.add_text("Nitro")
|
278
|
-
feed << generator
|
279
|
-
# icon
|
280
|
-
# logo
|
281
|
-
# rights
|
282
|
-
# subtitle
|
283
|
-
|
284
|
-
# Entries
|
285
|
-
objects.each do |o|
|
294
|
+
# id
|
295
|
+
if o.respond_to?(:to_href)
|
296
|
+
id = REXML::Element.new("id").add_text("#{options[:base]}#{fhref o}")
|
297
|
+
entry << id
|
298
|
+
end
|
286
299
|
|
287
|
-
#
|
288
|
-
|
289
|
-
|
300
|
+
# title
|
301
|
+
if o.respond_to?(:title)
|
302
|
+
title = REXML::Element.new("title").add_text(o.title)
|
303
|
+
entry << title
|
290
304
|
end
|
291
|
-
entry = REXML::Element.new("entry")
|
292
|
-
|
293
|
-
# Required entry elements
|
294
305
|
|
295
|
-
|
296
|
-
|
297
|
-
|
298
|
-
|
299
|
-
|
300
|
-
|
301
|
-
|
302
|
-
|
303
|
-
|
304
|
-
|
305
|
-
end
|
306
|
-
|
307
|
-
# updated
|
308
|
-
updated = Time.at(0) # a while back
|
309
|
-
if o.respond_to?(:update_time)
|
310
|
-
updated = o.update_time
|
311
|
-
elsif o.respond_to?(:create_time)
|
312
|
-
updated = o.create_time
|
313
|
-
elsif o.respond_to?(:date)
|
314
|
-
updated = o.date
|
315
|
-
end
|
316
|
-
entry << REXML::Element.new("updated").add_text(updated.iso8601)
|
317
|
-
|
318
|
-
# Recommended entry elements
|
306
|
+
# updated
|
307
|
+
updated = Time.at(0) # a while back
|
308
|
+
if o.respond_to?(:update_time)
|
309
|
+
updated = o.update_time
|
310
|
+
elsif o.respond_to?(:create_time)
|
311
|
+
updated = o.create_time
|
312
|
+
elsif o.respond_to?(:date)
|
313
|
+
updated = o.date
|
314
|
+
end
|
315
|
+
entry << REXML::Element.new("updated").add_text(updated.iso8601)
|
319
316
|
|
320
|
-
|
321
|
-
|
322
|
-
|
323
|
-
|
324
|
-
author_name = REXML::Element.new("name").add_text(o.author[:name])
|
325
|
-
author << author_name
|
326
|
-
if o.author[:email]
|
327
|
-
author_email = REXML::Element.new("email").add_text(o.author[:email])
|
328
|
-
author << author_email
|
329
|
-
end
|
330
|
-
if o.author[:link]
|
331
|
-
author_link = REXML::Element.new("uri").add_text(o.author[:link])
|
332
|
-
author << author_link
|
333
|
-
end
|
334
|
-
entry << author
|
335
|
-
end
|
336
|
-
end
|
317
|
+
# Recommended entry elements
|
318
|
+
|
319
|
+
# author
|
320
|
+
if o.respond_to?(:author)
|
337
321
|
|
338
|
-
|
339
|
-
|
340
|
-
|
341
|
-
|
342
|
-
summary.add_text(o.body.first_char(256).gsub(/<[^>]+>/, ' '))
|
343
|
-
entry << summary
|
322
|
+
if o.author.kind_of?(Hash)
|
323
|
+
oauthor = OpenStruct.new(o.author)
|
324
|
+
else
|
325
|
+
oauthor = o.author
|
344
326
|
end
|
345
327
|
|
346
|
-
|
347
|
-
|
348
|
-
|
349
|
-
|
350
|
-
|
351
|
-
|
352
|
-
entry << content
|
328
|
+
author = REXML::Element.new("author")
|
329
|
+
author_name = REXML::Element.new("name").add_text(oauthor.name)
|
330
|
+
author << author_name
|
331
|
+
if oauthor.email
|
332
|
+
author_email = REXML::Element.new("email").add_text(oauthor.email)
|
333
|
+
author << author_email
|
353
334
|
end
|
354
|
-
|
355
|
-
|
356
|
-
|
357
|
-
link = REXML::Element.new("link")
|
358
|
-
link.add_attributes({ "rel" => "alternate", "href" => "#{options[:base]}/#{o.to_href}" })
|
359
|
-
entry << link
|
335
|
+
if oauthor.link
|
336
|
+
author_link = REXML::Element.new("uri").add_text(oauthor.link)
|
337
|
+
author << author_link
|
360
338
|
end
|
361
|
-
|
362
|
-
|
339
|
+
entry << author
|
340
|
+
end
|
363
341
|
|
364
|
-
|
365
|
-
|
366
|
-
|
367
|
-
|
368
|
-
|
369
|
-
|
370
|
-
|
371
|
-
|
372
|
-
end
|
373
|
-
# source
|
374
|
-
# rights
|
342
|
+
# summary
|
343
|
+
|
344
|
+
if o.respond_to?(:body)
|
345
|
+
summary = REXML::Element.new("summary")
|
346
|
+
#TODO: think about whether 256 chars should be a fixed limit
|
347
|
+
summary.add_text(o.body.first_char(256).gsub(/<[^>]+>/, ' '))
|
348
|
+
entry << summary
|
349
|
+
end
|
375
350
|
|
376
|
-
#
|
377
|
-
|
351
|
+
# content
|
352
|
+
# may have the type text, html or xhtml
|
378
353
|
|
379
|
-
|
354
|
+
if o.respond_to?(:full_content)
|
355
|
+
content = REXML::Element.new("content")
|
356
|
+
#TODO: think about whether markup should always be done
|
357
|
+
content.add_text(markup(o.full_content))
|
358
|
+
entry << content
|
359
|
+
end
|
360
|
+
|
361
|
+
# link: An entry must contain an alternate link if there is no content element.
|
362
|
+
|
363
|
+
if o.respond_to?(:to_href)
|
364
|
+
link = REXML::Element.new("link")
|
365
|
+
link.add_attributes({ "rel" => "alternate", "href" => "#{options[:base]}#{fhref o}" })
|
366
|
+
entry << link
|
367
|
+
end
|
368
|
+
|
369
|
+
# Optional entry elements
|
380
370
|
|
381
|
-
|
382
|
-
|
383
|
-
|
384
|
-
|
385
|
-
|
371
|
+
# category
|
372
|
+
# could be used for Tags maybe?
|
373
|
+
# contributor
|
374
|
+
# published
|
375
|
+
|
376
|
+
if o.respond_to?(:create_time)
|
377
|
+
published = REXML::Element.new("published")
|
378
|
+
published.add_text(o.create_time.iso8601)
|
379
|
+
entry << published
|
380
|
+
end
|
381
|
+
|
382
|
+
# source
|
383
|
+
# rights
|
384
|
+
|
385
|
+
# don't forget to add the entry to the feed
|
386
|
+
feed << entry
|
387
|
+
|
388
|
+
end if objects.size > 0 # objects/entries
|
389
|
+
|
390
|
+
atom << feed
|
391
|
+
|
392
|
+
return atom.to_s
|
393
|
+
end
|
394
|
+
alias_method :atom, :build_atom
|
395
|
+
|
396
|
+
# OPML 1.0 feed lists
|
397
|
+
# Fabian: eww, who invented OPML? Who needs it? Implementing
|
398
|
+
# it in a very rough way anyway though. Takes a Hash of
|
399
|
+
# Feeds and optional options.
|
400
|
+
|
401
|
+
def build_opml(feedhash, options = {})
|
386
402
|
|
387
|
-
#
|
388
|
-
|
389
|
-
|
390
|
-
# Feeds and optional options.
|
403
|
+
# new XML Document for OPML
|
404
|
+
opml = REXML::Document.new
|
405
|
+
opml << REXML::XMLDecl.new("1.0", "utf-8")
|
391
406
|
|
392
|
-
|
393
|
-
|
394
|
-
|
395
|
-
opml = REXML::Document.new
|
396
|
-
opml << REXML::XMLDecl.new("1.0", "utf-8")
|
397
|
-
|
398
|
-
# Root element <opml />
|
399
|
-
opml = REXML::Element.new("opml")
|
400
|
-
opml.add_attribute("version", "1.0")
|
407
|
+
# Root element <opml />
|
408
|
+
opml = REXML::Element.new("opml")
|
409
|
+
opml.add_attribute("version", "1.0")
|
401
410
|
|
402
|
-
|
403
|
-
|
404
|
-
|
405
|
-
|
406
|
-
|
407
|
-
|
408
|
-
|
409
|
-
|
410
|
-
|
411
|
-
|
412
|
-
|
413
|
-
|
414
|
-
|
415
|
-
# body
|
416
|
-
body = REXML::Element.new("body")
|
417
|
-
feedhash.each do |url, type|
|
418
|
-
outline = REXML::Element.new("outline")
|
419
|
-
outline.add_attributes({ "type" => type, "xmlUrl" => url })
|
420
|
-
body << outline
|
421
|
-
end
|
422
|
-
opml << body
|
423
|
-
|
424
|
-
return opml.to_s
|
425
|
-
end # opml
|
426
|
-
alias_method :opml, :build_opml
|
411
|
+
# head
|
412
|
+
head = REXML::Element.new("head")
|
413
|
+
# title
|
414
|
+
if options[:title]
|
415
|
+
title = REXML::Element.new("title").add_text(options[:title])
|
416
|
+
head << title
|
417
|
+
end
|
418
|
+
# dateCreated
|
419
|
+
# dateModified
|
420
|
+
# ownerName
|
421
|
+
# ownerEmail
|
422
|
+
opml << head
|
427
423
|
|
424
|
+
# body
|
425
|
+
body = REXML::Element.new("body")
|
426
|
+
feedhash.each do |url, type|
|
427
|
+
outline = REXML::Element.new("outline")
|
428
|
+
outline.add_attributes({ "type" => type, "xmlUrl" => url })
|
429
|
+
body << outline
|
430
|
+
end
|
431
|
+
opml << body
|
432
|
+
|
433
|
+
return opml.to_s
|
434
|
+
end
|
435
|
+
alias_method :opml, :build_opml
|
436
|
+
|
437
|
+
|
438
|
+
# Helper
|
439
|
+
|
440
|
+
def fhref(obj)
|
441
|
+
"/#{obj.to_href}".squeeze('/')
|
428
442
|
end
|
443
|
+
|
429
444
|
end
|
430
445
|
|
431
|
-
|
432
|
-
# * George Moschovitis <gm@navel.gr>
|
446
|
+
end
|