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.
Files changed (174) hide show
  1. data/bin/nitro +135 -37
  2. data/doc/CHANGELOG.1 +108 -108
  3. data/doc/CHANGELOG.2 +89 -89
  4. data/doc/CHANGELOG.3 +105 -105
  5. data/{CHANGELOG → doc/CHANGELOG.4} +509 -509
  6. data/doc/{AUTHORS → CONTRIBUTORS} +49 -37
  7. data/doc/LIBRARIES +13 -0
  8. data/doc/LICENSE +2 -3
  9. data/doc/MIGRATION +45 -0
  10. data/doc/RELEASES +131 -11
  11. data/doc/TODO +67 -0
  12. data/lib/glue/magick.rb +0 -3
  13. data/lib/glue/sweeper.rb +30 -15
  14. data/lib/glue/thumbnails.rb +0 -2
  15. data/lib/glue/webfile.rb +23 -11
  16. data/lib/nitro.rb +37 -44
  17. data/lib/nitro/adapter/cgi.rb +0 -3
  18. data/lib/nitro/adapter/console.rb +0 -2
  19. data/lib/nitro/adapter/fastcgi.rb +6 -3
  20. data/lib/nitro/adapter/mongrel.rb +97 -58
  21. data/lib/nitro/adapter/script.rb +4 -6
  22. data/lib/nitro/adapter/webrick.rb +33 -87
  23. data/lib/nitro/adapter/webrick/vcr.rb +85 -0
  24. data/lib/nitro/caching.rb +0 -2
  25. data/lib/nitro/caching/actions.rb +0 -2
  26. data/lib/nitro/caching/fragments.rb +0 -2
  27. data/lib/nitro/caching/output.rb +45 -16
  28. data/lib/nitro/caching/proxy.rb +49 -0
  29. data/lib/nitro/cgi.rb +3 -6
  30. data/lib/nitro/cgi/cookie.rb +0 -3
  31. data/lib/nitro/cgi/request.rb +67 -24
  32. data/lib/nitro/cgi/response.rb +0 -2
  33. data/lib/nitro/cgi/{sendfile.rb → send_file.rb} +7 -6
  34. data/lib/nitro/compiler.rb +62 -55
  35. data/lib/nitro/compiler/cleanup.rb +0 -3
  36. data/lib/nitro/compiler/elements.rb +31 -28
  37. data/lib/nitro/compiler/errors.rb +2 -5
  38. data/lib/nitro/compiler/include.rb +10 -8
  39. data/lib/nitro/compiler/layout.rb +0 -2
  40. data/lib/nitro/compiler/localization.rb +0 -2
  41. data/lib/nitro/compiler/markup.rb +14 -6
  42. data/lib/nitro/compiler/morphing.rb +1 -5
  43. data/lib/nitro/compiler/script.rb +2 -4
  44. data/lib/nitro/compiler/squeeze.rb +0 -2
  45. data/lib/nitro/compiler/xslt.rb +0 -2
  46. data/lib/nitro/context.rb +10 -5
  47. data/lib/nitro/control.rb +18 -0
  48. data/lib/nitro/control/attribute.rb +88 -0
  49. data/lib/nitro/control/attribute/checkbox.rb +19 -0
  50. data/lib/nitro/control/attribute/datetime.rb +21 -0
  51. data/lib/nitro/control/attribute/file.rb +20 -0
  52. data/lib/nitro/control/attribute/fixnum.rb +26 -0
  53. data/lib/nitro/control/attribute/float.rb +26 -0
  54. data/lib/nitro/control/attribute/options.rb +38 -0
  55. data/lib/nitro/control/attribute/password.rb +16 -0
  56. data/lib/nitro/control/attribute/text.rb +16 -0
  57. data/lib/nitro/control/attribute/textarea.rb +16 -0
  58. data/lib/nitro/control/none.rb +16 -0
  59. data/lib/nitro/control/relation.rb +53 -0
  60. data/lib/nitro/control/relation/belongs_to.rb +0 -0
  61. data/lib/nitro/control/relation/has_many.rb +97 -0
  62. data/lib/nitro/control/relation/joins_many.rb +0 -0
  63. data/lib/nitro/control/relation/many_to_many.rb +0 -0
  64. data/lib/nitro/control/relation/refers_to.rb +29 -0
  65. data/lib/nitro/controller.rb +7 -296
  66. data/lib/nitro/dispatcher.rb +72 -34
  67. data/lib/nitro/element.rb +36 -10
  68. data/lib/nitro/element/javascript.rb +0 -2
  69. data/lib/nitro/flash.rb +23 -10
  70. data/lib/nitro/global.rb +36 -11
  71. data/lib/nitro/helper.rb +22 -8
  72. data/lib/nitro/helper/benchmark.rb +0 -2
  73. data/lib/nitro/helper/buffer.rb +0 -3
  74. data/lib/nitro/helper/css.rb +12 -0
  75. data/lib/nitro/helper/debug.rb +1 -3
  76. data/lib/nitro/helper/default.rb +1 -0
  77. data/lib/nitro/helper/feed.rb +400 -386
  78. data/lib/nitro/helper/form.rb +246 -116
  79. data/lib/nitro/helper/javascript.rb +28 -2
  80. data/lib/nitro/helper/javascript/morphing.rb +0 -2
  81. data/lib/nitro/helper/javascript/prototype.rb +0 -2
  82. data/lib/nitro/helper/javascript/scriptaculous.rb +0 -1
  83. data/lib/nitro/helper/layout.rb +0 -2
  84. data/lib/nitro/helper/navigation.rb +87 -0
  85. data/lib/nitro/helper/pager.rb +11 -22
  86. data/lib/nitro/helper/table.rb +9 -32
  87. data/lib/nitro/helper/url.rb +104 -0
  88. data/lib/nitro/helper/xhtml.rb +20 -4
  89. data/lib/nitro/helper/xml.rb +0 -2
  90. data/lib/nitro/markup.rb +131 -0
  91. data/lib/nitro/part.rb +52 -7
  92. data/lib/nitro/publishable.rb +328 -0
  93. data/lib/nitro/render.rb +30 -61
  94. data/lib/nitro/router.rb +12 -4
  95. data/lib/nitro/sanitize.rb +48 -0
  96. data/lib/nitro/scaffold.rb +9 -11
  97. data/lib/nitro/scaffold/controller.rb +25 -0
  98. data/lib/nitro/scaffold/model.rb +150 -0
  99. data/lib/nitro/scaffolding.rb +1 -3
  100. data/lib/nitro/server.rb +57 -32
  101. data/lib/nitro/server/drb.rb +16 -2
  102. data/lib/nitro/server/runner.rb +80 -102
  103. data/lib/nitro/service.rb +0 -1
  104. data/lib/nitro/service/xmlrpc.rb +0 -2
  105. data/lib/nitro/session.rb +26 -18
  106. data/lib/nitro/session/drb.rb +2 -16
  107. data/lib/nitro/session/memory.rb +0 -2
  108. data/lib/nitro/template.rb +219 -0
  109. data/lib/nitro/test/assertions.rb +1 -3
  110. data/lib/nitro/test/context.rb +0 -1
  111. data/lib/nitro/test/testcase.rb +0 -1
  112. data/lib/nitro/version.rb +6 -0
  113. data/lib/part/admin.rb +16 -0
  114. data/lib/part/admin/controller.rb +19 -0
  115. data/lib/part/admin/helper.rb +30 -0
  116. data/lib/part/admin/og/controller.rb +114 -0
  117. data/lib/part/admin/og/customize.rb +4 -0
  118. data/lib/part/admin/og/template/index.xhtml +27 -0
  119. data/lib/part/admin/og/template/list.xhtml +38 -0
  120. data/lib/part/admin/og/template/search.xhtml +20 -0
  121. data/lib/part/admin/og/template/update.xhtml +25 -0
  122. data/lib/part/admin/skin.rb +207 -0
  123. data/lib/part/admin/template/denied.xhtml +13 -0
  124. data/lib/part/admin/template/index.xhtml +12 -0
  125. data/lib/part/admin/todo.txt +2 -0
  126. data/proto/public/error.xhtml +4 -2
  127. data/proto/run.rb +0 -2
  128. data/test/glue/tc_webfile.rb +1 -0
  129. data/test/nitro/cgi/tc_request.rb +23 -0
  130. data/test/nitro/helper/tc_feed.rb +0 -3
  131. data/test/nitro/helper/tc_navbar.rb +74 -0
  132. data/test/nitro/helper/tc_table.rb +2 -0
  133. data/test/nitro/tc_cgi.rb +72 -19
  134. data/test/nitro/tc_controller.rb +35 -26
  135. data/test/nitro/tc_controller_aspect.rb +1 -0
  136. data/test/nitro/tc_controller_params.rb +864 -0
  137. data/test/nitro/tc_dispatcher.rb +2 -2
  138. data/test/nitro/tc_element.rb +16 -16
  139. data/test/nitro/tc_flash.rb +3 -3
  140. data/test/nitro/tc_markup.rb +31 -0
  141. data/test/nitro/tc_render.rb +12 -14
  142. data/test/nitro/tc_session.rb +9 -7
  143. data/test/nitro/tc_template.rb +34 -0
  144. metadata +217 -198
  145. data/INSTALL +0 -121
  146. data/ProjectInfo +0 -74
  147. data/README +0 -555
  148. data/doc/apache.txt +0 -9
  149. data/doc/config.txt +0 -28
  150. data/doc/faq.txt +0 -7
  151. data/doc/lhttpd.txt +0 -7
  152. data/lib/nitro/adapter/scgi.rb +0 -239
  153. data/lib/nitro/helper/form/builder.rb +0 -144
  154. data/lib/nitro/helper/form/controls.rb +0 -389
  155. data/lib/nitro/helper/rss.rb +0 -72
  156. data/proto/conf/apache.conf +0 -51
  157. data/proto/public/scaffold/advanced_search.xhtml +0 -30
  158. data/proto/public/scaffold/edit.xhtml +0 -11
  159. data/proto/public/scaffold/form.xhtml +0 -1
  160. data/proto/public/scaffold/index.xhtml +0 -20
  161. data/proto/public/scaffold/list.xhtml +0 -32
  162. data/proto/public/scaffold/new.xhtml +0 -11
  163. data/proto/public/scaffold/search.xhtml +0 -29
  164. data/proto/public/scaffold/view.xhtml +0 -8
  165. data/proto/script/scgi_ctl +0 -221
  166. data/proto/script/scgi_service +0 -128
  167. data/setup.rb +0 -1585
  168. data/src/part/admin.rb +0 -16
  169. data/src/part/admin/controller.rb +0 -81
  170. data/src/part/admin/skin.rb +0 -21
  171. data/src/part/admin/system.css +0 -135
  172. data/src/part/admin/template/denied.xhtml +0 -1
  173. data/src/part/admin/template/index.xhtml +0 -43
  174. data/test/nitro/helper/tc_rss.rb +0 -24
@@ -21,5 +21,3 @@ module BenchmarkHelper
21
21
  end
22
22
 
23
23
  end
24
-
25
- # * George Moschovitis <gm@navel.gr>
@@ -58,6 +58,3 @@ private
58
58
  end
59
59
 
60
60
  end
61
-
62
- # * George Moschovitis <gm@navel.gr>
63
-
@@ -0,0 +1,12 @@
1
+ module Nitro
2
+
3
+ # CSS Manager.
4
+ #--
5
+ # TODO: Add support for synthesizing compound CSS files from
6
+ # multiple smaller files.
7
+ #++
8
+
9
+ module CSS
10
+ end
11
+
12
+ end
@@ -16,7 +16,7 @@ private
16
16
  begin
17
17
  Marshal::dump(object)
18
18
  "<pre class='debug_dump'>#{object.to_yaml.gsub(" ", "&nbsp; ")}</pre>"
19
- rescue Object
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>
@@ -8,6 +8,7 @@ module Nitro
8
8
 
9
9
  module DefaultHelper
10
10
  include DebugHelper
11
+ private :DebugHelper
11
12
  # This is an open module, extend in your application.
12
13
  end
13
14
 
@@ -1,5 +1,5 @@
1
1
  require 'rss/maker'
2
- require 'glue/markup'
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
- # 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
- # )
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
- # default options
109
- options = {
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
- raise "Option ':version' contains a wrong version!" unless %w(0.9 0.91 1.0 2.0).include?(options[:version])
104
+ # RSS 0.91, 1.0, 2.0 feeds.
117
105
 
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
139
- end
140
- maker.channel.generator = "Nitro " + Nitro::Version.to_s
106
+ def build_rss(objects, options = {})
141
107
 
142
- maker.items.do_sort = true
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
- # 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]}/#{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
- # search form
189
- maker.textinput.title = options[:search_title] if options[:search_title]
190
- maker.textinput.description = options[:search_description] if options[:search_description]
191
- maker.textinput.name = options[:search_input_name] if options[:search_input_name]
192
- maker.textinput.link = options[:search_form_action] if options[:search_form_action]
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
- return rss.to_s
196
- end # rss
197
- alias_method :rss, :build_rss
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
- # Atom 1.0 feeds.
203
+ # default options
204
+ options = {
205
+ :title => 'Syndication',
206
+ }.update(options)
201
207
 
202
- def build_atom(objects, options = {})
203
-
204
- # default options
205
- options = {
206
- :title => 'Syndication',
207
- }.update(options)
208
-
209
- raise "first param must be a collection of objects!" unless objects.respond_to?(:to_ary)
210
- 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)
211
- raise "Option ':base' cannot be omitted!" unless options[:base]
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
- # Root element <feed />
218
- feed = REXML::Element.new("feed").add_namespace("http://www.w3.org/2005/Atom")
219
+ # Required feed elements
219
220
 
220
- # Required feed elements
221
-
222
- # id: Identifies the feed using a universally unique and permanent URI.
223
- iduri = URI.parse(options[:id] || options[:base]).normalize.to_s
224
- id = REXML::Element.new("id").add_text(iduri)
225
- feed << id
226
-
227
- # title: Contains a human readable title for the feed.
228
- title = REXML::Element.new("title").add_text(options[:title])
229
- feed << title
230
-
231
- # updated: Indicates the last time the feed was modified in a significant way.
232
- latest = Time.at(0) # a while back
233
- objects.each do |o|
234
- if o.respond_to?(:update_time)
235
- latest = o.update_time if o.update_time > latest
236
- elsif o.respond_to?(:create_time)
237
- latest = o.create_time if o.create_time > latest
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
- updated = REXML::Element.new("updated").add_text(latest.iso8601)
243
- feed << updated
244
-
245
- # Recommended feed elements
246
-
247
- # link: A feed should contain a link back to the feed itself.
248
- if options[:link]
249
- link = REXML::Element.new("link")
250
- link.add_attributes({ "rel" => "self", "href" => options[:link] })
251
- feed << link
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
- # author: Names one author of the feed.
255
- if options[:author_name] # name is required for author
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
- # Optional feed elements
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
- # category:
273
- # contributor:
274
- # generator: Identifies the software used to generate the feed.
275
- generator = REXML::Element.new("generator")
276
- generator.add_attributes({ "uri" => "http://www.nitroproject.org", "version" => Nitro::Version })
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
- # new Entry (called "item" in RSS)
288
- unless o.respond_to?(:to_href) and o.respond_to?(:title)
289
- next
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
- # id
296
- if o.respond_to?(:to_href)
297
- id = REXML::Element.new("id").add_text("#{options[:base]}/#{o.to_href}")
298
- entry << id
299
- end
300
-
301
- # title
302
- if o.respond_to?(:title)
303
- title = REXML::Element.new("title").add_text(o.title)
304
- entry << title
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
- # author
321
- if o.respond_to?(:author)
322
- if o.author[:name] # name is required for author
323
- author = REXML::Element.new("author")
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
- # summary
339
- if o.respond_to?(:body)
340
- summary = REXML::Element.new("summary")
341
- #TODO: think about whether 256 chars should be a fixed limit
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
- # content
347
- # may have the type text, html or xhtml
348
- if o.respond_to?(:full_content)
349
- content = REXML::Element.new("content")
350
- #TODO: think about whether markup should always be done
351
- content.add_text(markup(o.full_content))
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
- # link: An entry must contain an alternate link if there is no content element.
356
- if o.respond_to?(:to_href)
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
- # Optional entry elements
339
+ entry << author
340
+ end
363
341
 
364
- # category
365
- # could be used for Tags maybe?
366
- # contributor
367
- # published
368
- if o.respond_to?(:create_time)
369
- published = REXML::Element.new("published")
370
- published.add_text(o.create_time.iso8601)
371
- entry << published
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
- # don't forget to add the entry to the feed
377
- feed << entry
351
+ # content
352
+ # may have the type text, html or xhtml
378
353
 
379
- end if objects.size > 0 # objects/entries
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
- atom << feed
382
-
383
- return atom.to_s
384
- end # atom
385
- alias_method :atom, :build_atom
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
- # OPML 1.0 feed lists
388
- # Fabian: eww, who invented OPML? Who needs it? Implementing
389
- # it in a very rough way anyway though. Takes a Hash of
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
- def build_opml(feedhash, options = {})
393
-
394
- # new XML Document for OPML
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
- # head
403
- head = REXML::Element.new("head")
404
- # title
405
- if options[:title]
406
- title = REXML::Element.new("title").add_text(options[:title])
407
- head << title
408
- end
409
- # dateCreated
410
- # dateModified
411
- # ownerName
412
- # ownerEmail
413
- opml << head
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
- # * Fabian Buch <fabian@fabian-buch.de>
432
- # * George Moschovitis <gm@navel.gr>
446
+ end