raw 0.49.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.
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