diff --git a/tracks/vendor/plugins/resource_feeder/README b/tracks/vendor/plugins/resource_feeder/README new file mode 100644 index 00000000..5502be25 --- /dev/null +++ b/tracks/vendor/plugins/resource_feeder/README @@ -0,0 +1,7 @@ +ResourceFeeder +============== + +Simple feeds for resources + +NOTE: This plugin depends on the latest version of simply_helpful, available here: +http://dev.rubyonrails.org/svn/rails/plugins/simply_helpful/ diff --git a/tracks/vendor/plugins/resource_feeder/Rakefile b/tracks/vendor/plugins/resource_feeder/Rakefile new file mode 100644 index 00000000..51fce7b3 --- /dev/null +++ b/tracks/vendor/plugins/resource_feeder/Rakefile @@ -0,0 +1,22 @@ +require 'rake' +require 'rake/testtask' +require 'rake/rdoctask' + +desc 'Default: run unit tests.' +task :default => :test + +desc 'Test the resource_feed plugin.' +Rake::TestTask.new(:test) do |t| + t.libs << 'lib' + t.pattern = 'test/**/*_test.rb' + t.verbose = true +end + +desc 'Generate documentation for the resource_feed plugin.' +Rake::RDocTask.new(:rdoc) do |rdoc| + rdoc.rdoc_dir = 'rdoc' + rdoc.title = 'ResourceFeed' + rdoc.options << '--line-numbers' << '--inline-source' + rdoc.rdoc_files.include('README') + rdoc.rdoc_files.include('lib/**/*.rb') +end diff --git a/tracks/vendor/plugins/resource_feeder/init.rb b/tracks/vendor/plugins/resource_feeder/init.rb new file mode 100644 index 00000000..7b55d76f --- /dev/null +++ b/tracks/vendor/plugins/resource_feeder/init.rb @@ -0,0 +1,2 @@ +require 'resource_feeder' +ActionController::Base.send(:include, ResourceFeeder::Rss, ResourceFeeder::Atom) \ No newline at end of file diff --git a/tracks/vendor/plugins/resource_feeder/lib/resource_feeder.rb b/tracks/vendor/plugins/resource_feeder/lib/resource_feeder.rb new file mode 100644 index 00000000..b5003419 --- /dev/null +++ b/tracks/vendor/plugins/resource_feeder/lib/resource_feeder.rb @@ -0,0 +1,2 @@ +require 'resource_feeder/rss' +require 'resource_feeder/atom' diff --git a/tracks/vendor/plugins/resource_feeder/lib/resource_feeder/atom.rb b/tracks/vendor/plugins/resource_feeder/lib/resource_feeder/atom.rb new file mode 100644 index 00000000..d3b5a63c --- /dev/null +++ b/tracks/vendor/plugins/resource_feeder/lib/resource_feeder/atom.rb @@ -0,0 +1,78 @@ +module ResourceFeeder + module Atom + extend self + + def render_atom_feed_for(resources, options = {}) + render :text => atom_feed_for(resources, options), :content_type => Mime::ATOM + end + + def atom_feed_for(resources, options = {}) + xml = Builder::XmlMarkup.new(:indent => 2) + + options[:feed] ||= {} + options[:item] ||= {} + options[:url_writer] ||= self + + if options[:class] || resources.first + klass = options[:class] || resources.first.class + new_record = klass.new + else + options[:feed] = { :title => "Empty", :link => "http://example.com" } + end + + options[:feed][:title] ||= klass.name.pluralize + options[:feed][:id] ||= "tag:#{request.host_with_port}:#{klass.name.pluralize}" + options[:feed][:link] ||= SimplyHelpful::RecordIdentifier.polymorphic_url(new_record, options[:url_writer]) + + options[:item][:title] ||= [ :title, :subject, :headline, :name ] + options[:item][:description] ||= [ :description, :body, :content ] + options[:item][:pub_date] ||= [ :updated_at, :updated_on, :created_at, :created_on ] + options[:item][:author] ||= [ :author, :creator ] + + resource_link = lambda { |r| SimplyHelpful::RecordIdentifier.polymorphic_url(r, options[:url_writer]) } + + xml.instruct! + xml.feed "xml:lang" => "en-US", "xmlns" => 'http://www.w3.org/2005/Atom' do + xml.title(options[:feed][:title]) + xml.id(options[:feed][:id]) + xml.link(:rel => 'alternate', :type => 'text/html', :href => options[:feed][:link]) + xml.link(:rel => 'self', :type => 'application/atom+xml', :href => options[:feed][:self]) if options[:feed][:self] + xml.subtitle(options[:feed][:description]) if options[:feed][:description] + + for resource in resources + published_at = call_or_read(options[:item][:pub_date], resource) + + xml.entry do + xml.title(call_or_read(options[:item][:title], resource)) + xml.content(call_or_read(options[:item][:description], resource), :type => 'html') + xml.id("tag:#{request.host_with_port},#{published_at.xmlschema}:#{call_or_read(options[:item][:guid] || options[:item][:link] || resource_link, resource)}") + xml.published(published_at.xmlschema) + xml.updated((resource.respond_to?(:updated_at) ? call_or_read(options[:item][:pub_date] || :updated_at, resource) : published_at).xmlschema) + xml.link(:rel => 'alternate', :type => 'text/html', :href => call_or_read(options[:item][:link] || options[:item][:guid] || resource_link, resource)) + + if author = call_or_read(options[:item][:author], resource) + xml.author do + xml.name() + end + end + end + end + end + end + + private + def call_or_read(procedure_or_attributes, resource) + case procedure_or_attributes + when Array + attributes = procedure_or_attributes + resource.send(attributes.select { |a| resource.respond_to?(a) }.first) + when Symbol + attribute = procedure_or_attributes + resource.send(attribute) + when Proc + procedure = procedure_or_attributes + procedure.call(resource) + end + end + end +end \ No newline at end of file diff --git a/tracks/vendor/plugins/resource_feeder/lib/resource_feeder/rss.rb b/tracks/vendor/plugins/resource_feeder/lib/resource_feeder/rss.rb new file mode 100644 index 00000000..b66ec4a8 --- /dev/null +++ b/tracks/vendor/plugins/resource_feeder/lib/resource_feeder/rss.rb @@ -0,0 +1,79 @@ +module ResourceFeeder + module Rss + extend self + + def render_rss_feed_for(resources, options = {}) + render :text => rss_feed_for(resources, options), :content_type => Mime::RSS + end + + def rss_feed_for(resources, options = {}) + xml = Builder::XmlMarkup.new(:indent => 2) + + options[:feed] ||= {} + options[:item] ||= {} + options[:url_writer] ||= self + + if options[:class] || resources.first + klass = options[:class] || resources.first.class + new_record = klass.new + else + options[:feed] = { :title => "Empty", :link => "http://example.com" } + end + use_content_encoded = options[:item].has_key?(:content_encoded) + + options[:feed][:title] ||= klass.name.pluralize + options[:feed][:link] ||= SimplyHelpful::RecordIdentifier.polymorphic_url(new_record, options[:url_writer]) + options[:feed][:language] ||= "en-us" + options[:feed][:ttl] ||= "40" + + options[:item][:title] ||= [ :title, :subject, :headline, :name ] + options[:item][:description] ||= [ :description, :body, :content ] + options[:item][:pub_date] ||= [ :updated_at, :updated_on, :created_at, :created_on ] + + resource_link = lambda { |r| SimplyHelpful::RecordIdentifier.polymorphic_url(r, options[:url_writer]) } + + rss_root_attributes = { :version => 2.0 } + rss_root_attributes.merge!("xmlns:content" => "http://purl.org/rss/1.0/modules/content/") if use_content_encoded + + xml.instruct! + + xml.rss(rss_root_attributes) do + xml.channel do + xml.title(options[:feed][:title]) + xml.link(options[:feed][:link]) + xml.description(options[:feed][:description]) if options[:feed][:description] + xml.language(options[:feed][:language]) + xml.ttl(options[:feed][:ttl]) + + for resource in resources + xml.item do + xml.title(call_or_read(options[:item][:title], resource)) + xml.description(call_or_read(options[:item][:description], resource)) + if use_content_encoded then + xml.content(:encoded) { xml.cdata!(call_or_read(options[:item][:content_encoded], resource)) } + end + xml.pubDate(call_or_read(options[:item][:pub_date], resource).to_s(:rfc822)) + xml.guid(call_or_read(options[:item][:guid] || options[:item][:link] || resource_link, resource)) + xml.link(call_or_read(options[:item][:link] || options[:item][:guid] || resource_link, resource)) + end + end + end + end + end + + private + def call_or_read(procedure_or_attributes, resource) + case procedure_or_attributes + when Array + attributes = procedure_or_attributes + resource.send(attributes.select { |a| resource.respond_to?(a) }.first) + when Symbol + attribute = procedure_or_attributes + resource.send(attribute) + when Proc + procedure = procedure_or_attributes + procedure.call(resource) + end + end + end +end diff --git a/tracks/vendor/plugins/resource_feeder/test/atom_feed_test.rb b/tracks/vendor/plugins/resource_feeder/test/atom_feed_test.rb new file mode 100644 index 00000000..3112da47 --- /dev/null +++ b/tracks/vendor/plugins/resource_feeder/test/atom_feed_test.rb @@ -0,0 +1,85 @@ +require File.dirname(__FILE__) + '/test_helper' +class AtomFeedTest < Test::Unit::TestCase + attr_reader :request + + def setup + @request = OpenStruct.new + @request.host_with_port = 'example.com' + @records = Array.new(5).fill(Post.new) + @records.each &:save + end + + def test_default_atom_feed + atom_feed_for @records + + assert_select 'feed' do + assert_select '>title', 'Posts' + assert_select '>id', "tag:#{request.host_with_port}:Posts" + assert_select '>link' do + assert_select "[rel='alternate']" + assert_select "[type='text/html']" + assert_select "[href='http://example.com/posts']" + end + assert_select 'entry', 5 do + assert_select 'title', :text => 'feed title (title)' + assert_select "content[type='html']", '<p>feed description (description)</p>' + assert_select 'id', "tag:#{request.host_with_port},#{@records.first.created_at.xmlschema}:#{'http://example.com/posts/1'}" + assert_select 'published', @records.first.created_at.xmlschema + assert_select 'updated', @records.first.created_at.xmlschema + assert_select 'link' do + assert_select "[rel='alternate']" + assert_select "[type='text/html']" + assert_select "[href='http://example.com/posts/1']" + end + end + end + end + + def test_should_allow_custom_feed_options + atom_feed_for @records, :feed => { :title => 'Custom Posts', :link => '/posts', :description => 'stuff', :self => '/posts.atom' } + + assert_select 'feed>title', 'Custom Posts' + assert_select "feed>link[href='/posts']" + assert_select 'feed>subtitle', 'stuff' + assert_select 'feed>link' do + assert_select "[rel='self']" + assert_select "[type='application/atom+xml']" + assert_select "[href='/posts.atom']" + end + end + + def test_should_allow_custom_item_attributes + atom_feed_for @records, :item => { :title => :name, :description => :body, :pub_date => :create_date, :link => :id } + + assert_select 'entry', 5 do + assert_select 'title', :text => 'feed title (name)' + assert_select "content[type='html']", '<p>feed description (body)</p>' + assert_select 'published', (@records.first.created_at - 5.minutes).xmlschema + assert_select 'updated', (@records.first.created_at - 5.minutes).xmlschema + assert_select 'id', "tag:#{request.host_with_port},#{(@records.first.created_at - 5.minutes).xmlschema}:1" + assert_select 'link' do + assert_select "[rel='alternate']" + assert_select "[type='text/html']" + assert_select "[href='1']" + end + end + end + + def test_should_allow_custom_item_attribute_blocks + atom_feed_for @records, :item => { :title => lambda { |r| r.name }, :description => lambda { |r| r.body }, :pub_date => lambda { |r| r.create_date }, + :link => lambda { |r| "/#{r.created_at.to_i}" }, :guid => lambda { |r| r.created_at.to_i } } + + assert_select 'entry', 5 do + assert_select 'title', :text => 'feed title (name)' + assert_select "content[type='html']", '<p>feed description (body)</p>' + assert_select 'published', (@records.first.created_at - 5.minutes).xmlschema + assert_select 'updated', (@records.first.created_at - 5.minutes).xmlschema + assert_select 'id', /:\d+$/ + assert_select 'link' do + assert_select "[rel='alternate']" + assert_select "[type='text/html']" + assert_select "[href=?]", /^\/\d+$/ + end + end + end +end diff --git a/tracks/vendor/plugins/resource_feeder/test/rss_feed_test.rb b/tracks/vendor/plugins/resource_feeder/test/rss_feed_test.rb new file mode 100644 index 00000000..90525baf --- /dev/null +++ b/tracks/vendor/plugins/resource_feeder/test/rss_feed_test.rb @@ -0,0 +1,86 @@ +require File.dirname(__FILE__) + '/test_helper' +class RssFeedTest < Test::Unit::TestCase + def setup + @records = Array.new(5).fill(Post.new) + @records.each &:save + end + + def test_default_rss_feed + rss_feed_for @records + + assert_select 'rss[version="2.0"]' do + assert_select 'channel' do + assert_select '>title', 'Posts' + assert_select '>link', 'http://example.com/posts' + assert_select 'language', 'en-us' + assert_select 'ttl', '40' + end + assert_select 'item', 5 do + assert_select 'title', :text => 'feed title (title)' + assert_select 'description', '<p>feed description (description)</p>' + %w(guid link).each do |node| + assert_select node, 'http://example.com/posts/1' + end + assert_select 'pubDate', @records.first.created_at.to_s(:rfc822) + end + end + end + + def test_should_allow_custom_feed_options + rss_feed_for @records, :feed => { :title => 'Custom Posts', :link => '/posts', :description => 'stuff', :language => 'en-gb', :ttl => '80' } + + assert_select 'channel>title', 'Custom Posts' + assert_select 'channel>link', '/posts' + assert_select 'channel>description', 'stuff' + assert_select 'channel>language', 'en-gb' + assert_select 'channel>ttl', '80' + end + + def test_should_allow_custom_item_attributes + rss_feed_for @records, :item => { :title => :name, :description => :body, :pub_date => :create_date, :link => :id } + + assert_select 'item', 5 do + assert_select 'title', :text => 'feed title (name)' + assert_select 'description', '<p>feed description (body)</p>' + assert_select 'pubDate', (@records.first.created_at - 5.minutes).to_s(:rfc822) + assert_select 'link', '1' + assert_select 'guid', '1' + end + end + + def test_should_allow_custom_item_attribute_blocks + rss_feed_for @records, :item => { :title => lambda { |r| r.name }, :description => lambda { |r| r.body }, :pub_date => lambda { |r| r.create_date }, + :link => lambda { |r| "/#{r.created_at.to_i}" }, :guid => lambda { |r| r.created_at.to_i } } + + assert_select 'item', 5 do + assert_select 'title', :text => 'feed title (name)' + assert_select 'description', '<p>feed description (body)</p>' + assert_select 'pubDate', (@records.first.created_at - 5.minutes).to_s(:rfc822) + end + end + + # note that assert_select isnt easily able to get elements that have xml namespaces (as it thinks they are + # invalid html psuedo children), so we do some manual testing with the response body + def test_should_allow_content_encoded_for_items + rss_feed_for @records, :item => { :content_encoded => :full_html_body } + + html_content = "Here is some full content, with out any excerpts" + assert_equal 5, @response.body.scan("").size + assert_select 'item', 5 do + assert_select 'description + *', " { :content_encoded => :full_html_body } + assert_equal %[\n], + @response.body.grep(/\n], + @response.body.grep(/feed description (#{attr_name})

" + end + end + + def full_html_body + "Here is some full content, with out any excerpts" + end + + def create_date + @created_at - 5.minutes + end +end + +class Test::Unit::TestCase + include ResourceFeeder::Rss, ResourceFeeder::Atom + + def render_feed(xml) + @response = OpenStruct.new + @response.headers = {'Content-Type' => 'text/xml'} + @response.body = xml + end + + def rss_feed_for_with_ostruct(resources, options = {}) + render_feed rss_feed_for_without_ostruct(resources, options) + end + + def atom_feed_for_with_ostruct(resources, options = {}) + render_feed atom_feed_for_without_ostruct(resources, options) + end + + alias_method_chain :rss_feed_for, :ostruct + alias_method_chain :atom_feed_for, :ostruct + + def html_document + @html_document ||= HTML::Document.new(@response.body, false, true) + end + + def posts_url + "http://example.com/posts" + end + + def post_url(post) + "http://example.com/posts/#{post.id}" + end +end