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