nitro 0.29.0 → 0.30.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 (93) hide show
  1. data/CHANGELOG +410 -0
  2. data/ProjectInfo +36 -44
  3. data/README +5 -5
  4. data/doc/AUTHORS +6 -0
  5. data/doc/RELEASES +159 -2
  6. data/lib/glue/sweeper.rb +2 -2
  7. data/lib/glue/webfile.rb +14 -1
  8. data/lib/nitro.rb +6 -9
  9. data/lib/nitro/adapter/mongrel.rb +36 -43
  10. data/lib/nitro/adapter/scgi.rb +1 -1
  11. data/lib/nitro/adapter/webrick.rb +96 -24
  12. data/lib/nitro/caching/actions.rb +2 -1
  13. data/lib/nitro/caching/fragments.rb +1 -8
  14. data/lib/nitro/caching/output.rb +14 -4
  15. data/lib/nitro/cgi.rb +19 -21
  16. data/lib/nitro/cgi/cookie.rb +5 -1
  17. data/lib/nitro/cgi/request.rb +20 -4
  18. data/lib/nitro/compiler.rb +74 -28
  19. data/lib/nitro/compiler/cleanup.rb +1 -1
  20. data/lib/nitro/compiler/elements.rb +1 -2
  21. data/lib/nitro/compiler/localization.rb +1 -1
  22. data/lib/nitro/compiler/markup.rb +1 -1
  23. data/lib/nitro/compiler/script.rb +52 -44
  24. data/lib/nitro/compiler/squeeze.rb +4 -3
  25. data/lib/nitro/compiler/xslt.rb +7 -6
  26. data/lib/nitro/context.rb +39 -20
  27. data/lib/nitro/controller.rb +24 -5
  28. data/lib/nitro/dispatcher.rb +13 -5
  29. data/lib/nitro/global.rb +63 -0
  30. data/lib/nitro/helper/feed.rb +432 -0
  31. data/lib/nitro/helper/form.rb +11 -3
  32. data/lib/nitro/helper/form/builder.rb +140 -0
  33. data/lib/nitro/helper/form/controls.rb +2 -1
  34. data/lib/nitro/helper/javascript.rb +6 -0
  35. data/lib/nitro/helper/javascript/morphing.rb +13 -6
  36. data/lib/nitro/helper/xhtml.rb +42 -6
  37. data/lib/nitro/helper/xml.rb +3 -0
  38. data/lib/nitro/part.rb +2 -2
  39. data/lib/nitro/render.rb +7 -2
  40. data/lib/nitro/router.rb +57 -16
  41. data/lib/nitro/scaffolding.rb +29 -20
  42. data/lib/nitro/server.rb +4 -10
  43. data/lib/nitro/server/drb.rb +1 -1
  44. data/lib/nitro/server/runner.rb +10 -0
  45. data/lib/nitro/session.rb +31 -12
  46. data/lib/nitro/session/drb.rb +13 -1
  47. data/lib/nitro/session/file.rb +1 -1
  48. data/lib/nitro/session/memcached.rb +1 -1
  49. data/lib/nitro/session/memory.rb +1 -1
  50. data/lib/nitro/session/og.rb +1 -1
  51. data/lib/nitro/test/testcase.rb +3 -0
  52. data/proto/public/error.xhtml +5 -5
  53. data/proto/public/js/controls.js +2 -2
  54. data/proto/public/js/dragdrop.js +320 -79
  55. data/proto/public/js/effects.js +200 -152
  56. data/proto/public/js/prototype.js +284 -63
  57. data/proto/public/js/scriptaculous.js +7 -5
  58. data/proto/public/js/unittest.js +11 -0
  59. data/proto/public/scaffold/advanced_search.xhtml +30 -0
  60. data/proto/public/scaffold/list.xhtml +8 -1
  61. data/proto/public/scaffold/search.xhtml +2 -1
  62. data/proto/script/scgi_service +1 -1
  63. data/src/part/admin/controller.rb +1 -1
  64. data/src/part/admin/skin.rb +1 -1
  65. data/test/nitro/CONFIG.rb +3 -0
  66. data/test/nitro/adapter/tc_webrick.rb +1 -1
  67. data/test/nitro/cgi/tc_cookie.rb +1 -1
  68. data/test/nitro/cgi/tc_request.rb +5 -5
  69. data/test/nitro/compiler/tc_client_morpher.rb +47 -0
  70. data/test/nitro/compiler/tc_compiler.rb +2 -0
  71. data/test/nitro/helper/tc_feed.rb +138 -0
  72. data/test/nitro/helper/tc_pager.rb +1 -1
  73. data/test/nitro/helper/tc_rss.rb +1 -1
  74. data/test/nitro/helper/tc_table.rb +1 -1
  75. data/test/nitro/helper/tc_xhtml.rb +1 -1
  76. data/test/nitro/tc_caching.rb +1 -1
  77. data/test/nitro/tc_cgi.rb +1 -1
  78. data/test/nitro/tc_context.rb +1 -1
  79. data/test/nitro/tc_controller.rb +31 -3
  80. data/test/nitro/tc_controller_aspect.rb +1 -1
  81. data/test/nitro/tc_dispatcher.rb +1 -1
  82. data/test/nitro/tc_element.rb +1 -1
  83. data/test/nitro/tc_flash.rb +1 -1
  84. data/test/nitro/tc_helper.rb +1 -1
  85. data/test/nitro/tc_render.rb +6 -6
  86. data/test/nitro/tc_router.rb +8 -4
  87. data/test/nitro/tc_server.rb +1 -3
  88. data/test/nitro/tc_session.rb +1 -3
  89. metadata +107 -104
  90. data/Rakefile +0 -232
  91. data/lib/nitro/adapter/acgi.rb +0 -237
  92. data/proto/public/Makefile.acgi +0 -40
  93. data/proto/public/acgi.c +0 -138
@@ -0,0 +1,432 @@
1
+ require 'rss/maker'
2
+ require 'glue/markup'
3
+ require 'rexml/document'
4
+ require 'time'
5
+ require 'uri'
6
+
7
+ require 'facet/string/first_char'
8
+
9
+ module Nitro
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 = {})
107
+
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)
115
+
116
+ raise "Option ':version' contains a wrong version!" unless %w(0.9 0.91 1.0 2.0).include?(options[:version])
117
+
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
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]}/#{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
187
+
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]
193
+ end
194
+
195
+ return rss.to_s
196
+ end # rss
197
+ alias_method :rss, :build_rss
198
+
199
+
200
+ # Atom 1.0 feeds.
201
+
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")
216
+
217
+ # Root element <feed />
218
+ feed = REXML::Element.new("feed").add_namespace("http://www.w3.org/2005/Atom")
219
+
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
241
+ 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
252
+ 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
268
+ end
269
+
270
+ # Optional feed elements
271
+
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|
286
+
287
+ # new Entry (called "item" in RSS)
288
+ unless o.respond_to?(:to_href) and o.respond_to?(:title)
289
+ next
290
+ end
291
+ entry = REXML::Element.new("entry")
292
+
293
+ # Required entry elements
294
+
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
319
+
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
337
+
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
344
+ end
345
+
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
353
+ 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
360
+ end
361
+
362
+ # Optional entry elements
363
+
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
375
+
376
+ # don't forget to add the entry to the feed
377
+ feed << entry
378
+
379
+ end if objects.size > 0 # objects/entries
380
+
381
+ atom << feed
382
+
383
+ return atom.to_s
384
+ end # atom
385
+ alias_method :atom, :build_atom
386
+
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.
391
+
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")
401
+
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
427
+
428
+ end
429
+ end
430
+
431
+ # * Fabian Buch <fabian@fabian-buch.de>
432
+ # * George Moschovitis <gm@navel.gr>
@@ -1,6 +1,7 @@
1
1
  require 'facet/inflect'
2
2
 
3
3
  require 'nitro/helper/form/controls'
4
+ require 'nitro/helper/form/builder'
4
5
 
5
6
  module Nitro
6
7
 
@@ -86,9 +87,13 @@ module FormHelper
86
87
  end
87
88
  end
88
89
 
89
- action = "#{@base}/#{action}" unless action =~ /\//
90
+ if action == :self
91
+ str << %{<form method="post"#{enctype_attribute}>}
92
+ else
93
+ action = "#{@base}/#{action}" unless action =~ /\//
94
+ str << %{<form action="#{action}" method="post"#{enctype_attribute}>}
95
+ end
90
96
 
91
- str << %{<form action="#{action}" method="post"#{enctype_attribute}>}
92
97
  str << %{<input type="hidden" name="oid" value="#{obj.oid}" />} if obj.oid
93
98
  str << controls_for(obj, options)
94
99
  str << %{<input type="submit" value="#{submit}" />}
@@ -104,7 +109,10 @@ module FormHelper
104
109
  str = FormBuilder.prologue
105
110
 
106
111
  controls_for_properties(str, obj, options)
107
- controls_for_relations(str, obj, options)
112
+
113
+ unless options[:no_relations]
114
+ controls_for_relations(str, obj, options)
115
+ end
108
116
 
109
117
  str << FormBuilder.epilogue
110
118