nitro 0.31.0 → 0.40.0
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/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
|