raw 0.49.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (148) hide show
  1. data/doc/CONTRIBUTORS +106 -0
  2. data/doc/LICENSE +32 -0
  3. data/doc/coding_conventions.txt +11 -0
  4. data/lib/raw.rb +42 -0
  5. data/lib/raw/adapter.rb +113 -0
  6. data/lib/raw/adapter/cgi.rb +41 -0
  7. data/lib/raw/adapter/fastcgi.rb +48 -0
  8. data/lib/raw/adapter/mongrel.rb +146 -0
  9. data/lib/raw/adapter/script.rb +94 -0
  10. data/lib/raw/adapter/webrick.rb +144 -0
  11. data/lib/raw/adapter/webrick/vcr.rb +91 -0
  12. data/lib/raw/cgi.rb +323 -0
  13. data/lib/raw/cgi/cookie.rb +47 -0
  14. data/lib/raw/cgi/http.rb +62 -0
  15. data/lib/raw/compiler.rb +138 -0
  16. data/lib/raw/compiler/filter/cleanup.rb +21 -0
  17. data/lib/raw/compiler/filter/elements.rb +166 -0
  18. data/lib/raw/compiler/filter/elements/element.rb +210 -0
  19. data/lib/raw/compiler/filter/localization.rb +23 -0
  20. data/lib/raw/compiler/filter/markup.rb +32 -0
  21. data/lib/raw/compiler/filter/morph.rb +123 -0
  22. data/lib/raw/compiler/filter/morph/each.rb +34 -0
  23. data/lib/raw/compiler/filter/morph/for.rb +11 -0
  24. data/lib/raw/compiler/filter/morph/if.rb +26 -0
  25. data/lib/raw/compiler/filter/morph/selected_if.rb +43 -0
  26. data/lib/raw/compiler/filter/morph/standard.rb +55 -0
  27. data/lib/raw/compiler/filter/morph/times.rb +27 -0
  28. data/lib/raw/compiler/filter/script.rb +116 -0
  29. data/lib/raw/compiler/filter/squeeze.rb +16 -0
  30. data/lib/raw/compiler/filter/static_include.rb +74 -0
  31. data/lib/raw/compiler/filter/template.rb +121 -0
  32. data/lib/raw/compiler/reloader.rb +96 -0
  33. data/lib/raw/context.rb +154 -0
  34. data/lib/raw/context/flash.rb +157 -0
  35. data/lib/raw/context/global.rb +88 -0
  36. data/lib/raw/context/request.rb +338 -0
  37. data/lib/raw/context/response.rb +57 -0
  38. data/lib/raw/context/session.rb +198 -0
  39. data/lib/raw/context/session/drb.rb +11 -0
  40. data/lib/raw/context/session/file.rb +15 -0
  41. data/lib/raw/context/session/memcached.rb +13 -0
  42. data/lib/raw/context/session/memory.rb +12 -0
  43. data/lib/raw/context/session/og.rb +15 -0
  44. data/lib/raw/context/session/pstore.rb +13 -0
  45. data/lib/raw/control.rb +18 -0
  46. data/lib/raw/control/attribute.rb +91 -0
  47. data/lib/raw/control/attribute/checkbox.rb +25 -0
  48. data/lib/raw/control/attribute/datetime.rb +21 -0
  49. data/lib/raw/control/attribute/file.rb +20 -0
  50. data/lib/raw/control/attribute/fixnum.rb +26 -0
  51. data/lib/raw/control/attribute/float.rb +26 -0
  52. data/lib/raw/control/attribute/options.rb +38 -0
  53. data/lib/raw/control/attribute/password.rb +16 -0
  54. data/lib/raw/control/attribute/text.rb +16 -0
  55. data/lib/raw/control/attribute/textarea.rb +16 -0
  56. data/lib/raw/control/none.rb +16 -0
  57. data/lib/raw/control/relation.rb +59 -0
  58. data/lib/raw/control/relation/belongs_to.rb +0 -0
  59. data/lib/raw/control/relation/has_many.rb +97 -0
  60. data/lib/raw/control/relation/joins_many.rb +0 -0
  61. data/lib/raw/control/relation/many_to_many.rb +0 -0
  62. data/lib/raw/control/relation/refers_to.rb +29 -0
  63. data/lib/raw/controller.rb +37 -0
  64. data/lib/raw/controller/publishable.rb +160 -0
  65. data/lib/raw/dispatcher.rb +209 -0
  66. data/lib/raw/dispatcher/format.rb +108 -0
  67. data/lib/raw/dispatcher/format/atom.rb +31 -0
  68. data/lib/raw/dispatcher/format/css.rb +0 -0
  69. data/lib/raw/dispatcher/format/html.rb +42 -0
  70. data/lib/raw/dispatcher/format/json.rb +31 -0
  71. data/lib/raw/dispatcher/format/rss.rb +33 -0
  72. data/lib/raw/dispatcher/format/xoxo.rb +31 -0
  73. data/lib/raw/dispatcher/mounter.rb +60 -0
  74. data/lib/raw/dispatcher/router.rb +111 -0
  75. data/lib/raw/errors.rb +19 -0
  76. data/lib/raw/helper.rb +86 -0
  77. data/lib/raw/helper/benchmark.rb +23 -0
  78. data/lib/raw/helper/buffer.rb +60 -0
  79. data/lib/raw/helper/cookie.rb +32 -0
  80. data/lib/raw/helper/debug.rb +28 -0
  81. data/lib/raw/helper/default.rb +16 -0
  82. data/lib/raw/helper/feed.rb +451 -0
  83. data/lib/raw/helper/form.rb +284 -0
  84. data/lib/raw/helper/javascript.rb +59 -0
  85. data/lib/raw/helper/layout.rb +40 -0
  86. data/lib/raw/helper/navigation.rb +87 -0
  87. data/lib/raw/helper/pager.rb +305 -0
  88. data/lib/raw/helper/table.rb +247 -0
  89. data/lib/raw/helper/xhtml.rb +218 -0
  90. data/lib/raw/helper/xml.rb +125 -0
  91. data/lib/raw/mixin/magick.rb +35 -0
  92. data/lib/raw/mixin/sweeper.rb +71 -0
  93. data/lib/raw/mixin/thumbnails.rb +1 -0
  94. data/lib/raw/mixin/webfile.rb +165 -0
  95. data/lib/raw/render.rb +271 -0
  96. data/lib/raw/render/builder.rb +26 -0
  97. data/lib/raw/render/caching.rb +81 -0
  98. data/lib/raw/render/call.rb +43 -0
  99. data/lib/raw/render/send_file.rb +46 -0
  100. data/lib/raw/render/stream.rb +39 -0
  101. data/lib/raw/scaffold.rb +13 -0
  102. data/lib/raw/scaffold/controller.rb +25 -0
  103. data/lib/raw/scaffold/model.rb +157 -0
  104. data/lib/raw/test.rb +5 -0
  105. data/lib/raw/test/assertions.rb +169 -0
  106. data/lib/raw/test/context.rb +55 -0
  107. data/lib/raw/test/testcase.rb +79 -0
  108. data/lib/raw/util/attr.rb +128 -0
  109. data/lib/raw/util/encode_uri.rb +149 -0
  110. data/lib/raw/util/html_filter.rb +538 -0
  111. data/lib/raw/util/markup.rb +130 -0
  112. data/test/glue/tc_webfile.rb +1 -0
  113. data/test/nitro/CONFIG.rb +3 -0
  114. data/test/nitro/adapter/raw_post1.bin +9 -0
  115. data/test/nitro/adapter/tc_webrick.rb +16 -0
  116. data/test/nitro/cgi/tc_cookie.rb +14 -0
  117. data/test/nitro/cgi/tc_request.rb +61 -0
  118. data/test/nitro/compiler/tc_client_morpher.rb +47 -0
  119. data/test/nitro/compiler/tc_compiler.rb +25 -0
  120. data/test/nitro/dispatcher/tc_mounter.rb +47 -0
  121. data/test/nitro/helper/tc_feed.rb +135 -0
  122. data/test/nitro/helper/tc_navbar.rb +74 -0
  123. data/test/nitro/helper/tc_pager.rb +35 -0
  124. data/test/nitro/helper/tc_table.rb +68 -0
  125. data/test/nitro/helper/tc_xhtml.rb +19 -0
  126. data/test/nitro/tc_caching.rb +19 -0
  127. data/test/nitro/tc_cgi.rb +222 -0
  128. data/test/nitro/tc_context.rb +17 -0
  129. data/test/nitro/tc_controller.rb +103 -0
  130. data/test/nitro/tc_controller_aspect.rb +32 -0
  131. data/test/nitro/tc_controller_params.rb +885 -0
  132. data/test/nitro/tc_dispatcher.rb +109 -0
  133. data/test/nitro/tc_element.rb +85 -0
  134. data/test/nitro/tc_flash.rb +59 -0
  135. data/test/nitro/tc_helper.rb +47 -0
  136. data/test/nitro/tc_render.rb +119 -0
  137. data/test/nitro/tc_router.rb +61 -0
  138. data/test/nitro/tc_server.rb +35 -0
  139. data/test/nitro/tc_session.rb +66 -0
  140. data/test/nitro/tc_template.rb +71 -0
  141. data/test/nitro/util/tc_encode_url.rb +87 -0
  142. data/test/nitro/util/tc_markup.rb +31 -0
  143. data/test/public/blog/another/very_litle/index.xhtml +1 -0
  144. data/test/public/blog/inc1.xhtml +2 -0
  145. data/test/public/blog/inc2.xhtml +1 -0
  146. data/test/public/blog/list.xhtml +9 -0
  147. data/test/public/dummy_mailer/registration.xhtml +5 -0
  148. 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(" ", "&nbsp; ")}</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