thoughtafter-simple-rss 1.2.3.1

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.
data/Rakefile ADDED
@@ -0,0 +1,212 @@
1
+ require 'rubygems'
2
+ require 'rake'
3
+ require 'rake/testtask'
4
+ require 'rake/rdoctask'
5
+ require 'rake/gempackagetask'
6
+ require 'rake/contrib/rubyforgepublisher'
7
+ require File.dirname(__FILE__) + '/lib/simple-rss'
8
+
9
+ PKG_VERSION = SimpleRSS::VERSION
10
+ PKG_NAME = "simple-rss"
11
+ PKG_FILE_NAME = "#{PKG_NAME}-#{PKG_VERSION}"
12
+ RUBY_FORGE_PROJECT = "simple-rss"
13
+ RUBY_FORGE_USER = ENV['RUBY_FORGE_USER'] || "cardmagic"
14
+ RELEASE_NAME = "#{PKG_NAME}-#{PKG_VERSION}"
15
+
16
+ PKG_FILES = FileList[
17
+ "lib/*", "bin/*", "test/**/*", "[A-Z]*", "Rakefile", "html/**/*"
18
+ ]
19
+
20
+ desc "Default Task"
21
+ task :default => [ :test ]
22
+
23
+ # Run the unit tests
24
+ desc "Run all unit tests"
25
+ Rake::TestTask.new("test") { |t|
26
+ t.libs << "lib"
27
+ t.pattern = 'test/*/*_test.rb'
28
+ t.verbose = true
29
+ }
30
+
31
+ # Make a console, useful when working on tests
32
+ desc "Generate a test console"
33
+ task :console do
34
+ verbose( false ) { sh "irb -I lib/ -r 'simple-rss'" }
35
+ end
36
+
37
+ # Genereate the RDoc documentation
38
+ desc "Create documentation"
39
+ Rake::RDocTask.new("doc") { |rdoc|
40
+ rdoc.title = "Simple RSS - A Flexible RSS and Atom reader for Ruby"
41
+ rdoc.rdoc_dir = 'html'
42
+ rdoc.rdoc_files.include('README')
43
+ rdoc.rdoc_files.include('lib/*.rb')
44
+ }
45
+
46
+ # Genereate the package
47
+ spec = Gem::Specification.new do |s|
48
+
49
+ #### Basic information.
50
+
51
+ s.name = 'simple-rss'
52
+ s.version = PKG_VERSION
53
+ s.summary = <<-EOF
54
+ A simple, flexible, extensible, and liberal RSS and Atom reader for Ruby. It is designed to be backwards compatible with the standard RSS parser, but will never do RSS generation.
55
+ EOF
56
+ s.description = <<-EOF
57
+ A simple, flexible, extensible, and liberal RSS and Atom reader for Ruby. It is designed to be backwards compatible with the standard RSS parser, but will never do RSS generation.
58
+ EOF
59
+
60
+ #### Which files are to be included in this gem? Everything! (Except CVS directories.)
61
+
62
+ s.files = PKG_FILES
63
+
64
+ #### Load-time details: library and application (you will need one or both).
65
+
66
+ s.require_path = 'lib'
67
+
68
+ #### Documentation and testing.
69
+
70
+ s.has_rdoc = true
71
+
72
+ #### Author and project details.
73
+
74
+ s.author = "Lucas Carlson"
75
+ s.email = "lucas@rufy.com"
76
+ s.homepage = "http://simple-rss.rubyforge.org/"
77
+ end
78
+
79
+ Rake::GemPackageTask.new(spec) do |pkg|
80
+ pkg.need_zip = true
81
+ pkg.need_tar = true
82
+ end
83
+
84
+ desc "Report code statistics (KLOCs, etc) from the application"
85
+ task :stats do
86
+ require 'code_statistics'
87
+ CodeStatistics.new(
88
+ ["Library", "lib"],
89
+ ["Units", "test"]
90
+ ).to_s
91
+ end
92
+
93
+ desc "Publish new documentation"
94
+ task :publish do
95
+ Rake::RubyForgePublisher.new('simple-rss', 'cardmagic').upload
96
+ end
97
+
98
+
99
+ desc "Publish the release files to RubyForge."
100
+ task :upload => [:package] do
101
+ files = ["gem", "tar.gz", "zip"].map { |ext| "pkg/#{PKG_FILE_NAME}.#{ext}" }
102
+
103
+ if RUBY_FORGE_PROJECT then
104
+ require 'net/http'
105
+ require 'open-uri'
106
+
107
+ project_uri = "http://rubyforge.org/projects/#{RUBY_FORGE_PROJECT}/"
108
+ project_data = open(project_uri) { |data| data.read }
109
+ group_id = project_data[/[?&]group_id=(\d+)/, 1]
110
+ raise "Couldn't get group id" unless group_id
111
+
112
+ # This echos password to shell which is a bit sucky
113
+ if ENV["RUBY_FORGE_PASSWORD"]
114
+ password = ENV["RUBY_FORGE_PASSWORD"]
115
+ else
116
+ print "#{RUBY_FORGE_USER}@rubyforge.org's password: "
117
+ password = STDIN.gets.chomp
118
+ end
119
+
120
+ login_response = Net::HTTP.start("rubyforge.org", 80) do |http|
121
+ data = [
122
+ "login=1",
123
+ "form_loginname=#{RUBY_FORGE_USER}",
124
+ "form_pw=#{password}"
125
+ ].join("&")
126
+ http.post("/account/login.php", data)
127
+ end
128
+
129
+ cookie = login_response["set-cookie"]
130
+ raise "Login failed" unless cookie
131
+ headers = { "Cookie" => cookie }
132
+
133
+ release_uri = "http://rubyforge.org/frs/admin/?group_id=#{group_id}"
134
+ release_data = open(release_uri, headers) { |data| data.read }
135
+ package_id = release_data[/[?&]package_id=(\d+)/, 1]
136
+ raise "Couldn't get package id" unless package_id
137
+
138
+ first_file = true
139
+ release_id = ""
140
+
141
+ files.each do |filename|
142
+ basename = File.basename(filename)
143
+ file_ext = File.extname(filename)
144
+ file_data = File.open(filename, "rb") { |file| file.read }
145
+
146
+ puts "Releasing #{basename}..."
147
+
148
+ release_response = Net::HTTP.start("rubyforge.org", 80) do |http|
149
+ release_date = Time.now.strftime("%Y-%m-%d %H:%M")
150
+ type_map = {
151
+ ".zip" => "3000",
152
+ ".tgz" => "3110",
153
+ ".gz" => "3110",
154
+ ".gem" => "1400"
155
+ }; type_map.default = "9999"
156
+ type = type_map[file_ext]
157
+ boundary = "rubyqMY6QN9bp6e4kS21H4y0zxcvoor"
158
+
159
+ query_hash = if first_file then
160
+ {
161
+ "group_id" => group_id,
162
+ "package_id" => package_id,
163
+ "release_name" => RELEASE_NAME,
164
+ "release_date" => release_date,
165
+ "type_id" => type,
166
+ "processor_id" => "8000", # Any
167
+ "release_notes" => "",
168
+ "release_changes" => "",
169
+ "preformatted" => "1",
170
+ "submit" => "1"
171
+ }
172
+ else
173
+ {
174
+ "group_id" => group_id,
175
+ "release_id" => release_id,
176
+ "package_id" => package_id,
177
+ "step2" => "1",
178
+ "type_id" => type,
179
+ "processor_id" => "8000", # Any
180
+ "submit" => "Add This File"
181
+ }
182
+ end
183
+
184
+ query = "?" + query_hash.map do |(name, value)|
185
+ [name, URI.encode(value)].join("=")
186
+ end.join("&")
187
+
188
+ data = [
189
+ "--" + boundary,
190
+ "Content-Disposition: form-data; name=\"userfile\"; filename=\"#{basename}\"",
191
+ "Content-Type: application/octet-stream",
192
+ "Content-Transfer-Encoding: binary",
193
+ "", file_data, ""
194
+ ].join("\x0D\x0A")
195
+
196
+ release_headers = headers.merge(
197
+ "Content-Type" => "multipart/form-data; boundary=#{boundary}"
198
+ )
199
+
200
+ target = first_file ? "/frs/admin/qrs.php" : "/frs/admin/editrelease.php"
201
+ http.post(target + query, data, release_headers)
202
+ end
203
+
204
+ if first_file then
205
+ release_id = release_response.body[/release_id=(\d+)/, 1]
206
+ raise("Couldn't get release id") unless release_id
207
+ end
208
+
209
+ first_file = false
210
+ end
211
+ end
212
+ end
data/install.rb ADDED
@@ -0,0 +1,40 @@
1
+ require 'rbconfig'
2
+ require 'find'
3
+ require 'fileutils'
4
+
5
+ include Config
6
+
7
+ # this was adapted from rdoc's install.rb by ways of Log4r
8
+
9
+ $sitedir = CONFIG["sitelibdir"]
10
+ unless $sitedir
11
+ version = CONFIG["MAJOR"] + "." + CONFIG["MINOR"]
12
+ $libdir = File.join(CONFIG["libdir"], "ruby", version)
13
+ $sitedir = $:.find {|x| x =~ /site_ruby/ }
14
+ if !$sitedir
15
+ $sitedir = File.join($libdir, "site_ruby")
16
+ elsif $sitedir !~ Regexp.quote(version)
17
+ $sitedir = File.join($sitedir, version)
18
+ end
19
+ end
20
+
21
+ makedirs = %w{ shipping }
22
+ makedirs.each {|f| File::makedirs(File.join($sitedir, *f.split(/\//)))}
23
+
24
+ Dir.chdir("lib")
25
+ begin
26
+ require 'rubygems'
27
+ require 'rake'
28
+ rescue LoadError
29
+ puts
30
+ puts "Please install Gem and Rake from http://rubyforge.org/projects/rubygems and http://rubyforge.org/projects/rake"
31
+ puts
32
+ exit(-1)
33
+ end
34
+
35
+ files = FileList["**/*"]
36
+
37
+ # File::safe_unlink *deprecated.collect{|f| File.join($sitedir, f.split(/\//))}
38
+ files.each {|f|
39
+ File::install(f, File.join($sitedir, *f.split(/\//)), 0644, true)
40
+ }
data/lib/simple-rss.rb ADDED
@@ -0,0 +1,162 @@
1
+ require 'cgi'
2
+ require 'time'
3
+
4
+ class SimpleRSS
5
+ VERSION = "1.2.3"
6
+
7
+ attr_reader :items, :source
8
+ alias :entries :items
9
+
10
+ @@feed_tags = [
11
+ :id,
12
+ :title, :subtitle, :link,
13
+ :description,
14
+ :author, :webMaster, :managingEditor, :contributor,
15
+ :pubDate, :lastBuildDate, :updated, :'dc:date',
16
+ :generator, :language, :docs, :cloud,
17
+ :ttl, :skipHours, :skipDays,
18
+ :image, :logo, :icon, :rating,
19
+ :rights, :copyright,
20
+ :textInput, :'feedburner:browserFriendly',
21
+ :'itunes:author', :'itunes:category'
22
+ ]
23
+
24
+ @@item_tags = [
25
+ :id,
26
+ :title, :link, :'link+alternate', :'link+self', :'link+edit', :'link+replies',
27
+ :author, :contributor,
28
+ :description, :summary, :content, :'content:encoded', :comments,
29
+ :pubDate, :published, :updated, :expirationDate, :modified, :'dc:date',
30
+ :category, :guid,
31
+ :'trackback:ping', :'trackback:about',
32
+ :'dc:creator', :'dc:title', :'dc:subject', :'dc:rights', :'dc:publisher',
33
+ :'feedburner:origLink',
34
+ :'media:content#url', :'media:content#type', :'media:content#height', :'media:content#width',
35
+ :'media:title', :'media:thumbnail#url', :'media:thumbnail#height', :'media:thumbnail#width',
36
+ :'media:credit', :'media:credit#role',
37
+ :'media:category', :'media:category#scheme'
38
+ ]
39
+
40
+ def initialize(source, options={})
41
+ @source = source.respond_to?(:read) ? source.read : source.to_s
42
+ @items = Array.new
43
+ @options = Hash.new.update(options)
44
+
45
+ parse
46
+ end
47
+
48
+ def channel() self end
49
+ alias :feed :channel
50
+
51
+ class << self
52
+ def feed_tags
53
+ @@feed_tags
54
+ end
55
+ def feed_tags=(ft)
56
+ @@feed_tags = ft
57
+ end
58
+
59
+ def item_tags
60
+ @@item_tags
61
+ end
62
+ def item_tags=(it)
63
+ @@item_tags = it
64
+ end
65
+
66
+ # The strict attribute is for compatibility with Ruby's standard RSS parser
67
+ def parse(source, options={})
68
+ new source, options
69
+ end
70
+ end
71
+
72
+ def parse
73
+ raise SimpleRSSError, "Poorly formatted feed" unless @source =~ %r{<(channel|feed).*?>.*?</(channel|feed)>}mi
74
+
75
+ # Feed's title and link
76
+ feed_content = $1 if @source =~ %r{(.*?)<(rss:|atom:)?(item|entry).*?>.*?</(rss:|atom:)?(item|entry)>}mi
77
+
78
+ @@feed_tags.each do |tag|
79
+ if feed_content && feed_content =~ %r{<(rss:|atom:)?#{tag}(.*?)>(.*?)</(rss:|atom:)?#{tag}>}mi
80
+ nil
81
+ elsif feed_content && feed_content =~ %r{<(rss:|atom:)?#{tag}(.*?)\/\s*>}mi
82
+ nil
83
+ elsif @source =~ %r{<(rss:|atom:)?#{tag}(.*?)>(.*?)</(rss:|atom:)?#{tag}>}mi
84
+ nil
85
+ elsif @source =~ %r{<(rss:|atom:)?#{tag}(.*?)\/\s*>}mi
86
+ nil
87
+ end
88
+
89
+ if $2 || $3
90
+ tag_cleaned = clean_tag(tag)
91
+ instance_variable_set("@#{ tag_cleaned }", clean_content(tag, $2, $3))
92
+ self.class.send(:attr_reader, tag_cleaned)
93
+ end
94
+ end
95
+
96
+ # RSS items' title, link, and description
97
+ @source.scan( %r{<(rss:|atom:)?(item|entry)([\s][^>]*)?>(.*?)</(rss:|atom:)?(item|entry)>}mi ) do |match|
98
+ item = Hash.new
99
+ @@item_tags.each do |tag|
100
+ if tag.to_s.include?("+")
101
+ tag_data = tag.to_s.split("+")
102
+ tag = tag_data[0]
103
+ rel = tag_data[1]
104
+
105
+ if match[3] =~ %r{<(rss:|atom:)?#{tag}(.*?)rel=['"]#{rel}['"](.*?)>(.*?)</(rss:|atom:)?#{tag}>}mi
106
+ nil
107
+ elsif match[3] =~ %r{<(rss:|atom:)?#{tag}(.*?)rel=['"]#{rel}['"](.*?)/\s*>}mi
108
+ nil
109
+ end
110
+ item[clean_tag("#{tag}+#{rel}")] = clean_content(tag, $3, $4) if $3 || $4
111
+ elsif tag.to_s.include?("#")
112
+ tag_data = tag.to_s.split("#")
113
+ tag = tag_data[0]
114
+ attrib = tag_data[1]
115
+ if match[3] =~ %r{<(rss:|atom:)?#{tag}(.*?)#{attrib}=['"](.*?)['"](.*?)>(.*?)</(rss:|atom:)?#{tag}>}mi
116
+ nil
117
+ elsif match[3] =~ %r{<(rss:|atom:)?#{tag}(.*?)#{attrib}=['"](.*?)['"](.*?)/\s*>}mi
118
+ nil
119
+ end
120
+ item[clean_tag("#{tag}_#{attrib}")] = clean_content(tag, attrib, $3) if $3
121
+ else
122
+ if match[3] =~ %r{<(rss:|atom:)?#{tag}(.*?)>(.*?)</(rss:|atom:)?#{tag}>}mi
123
+ nil
124
+ elsif match[3] =~ %r{<(rss:|atom:)?#{tag}(.*?)/\s*>}mi
125
+ nil
126
+ end
127
+ item[clean_tag(tag)] = clean_content(tag, $2, $3) if $2 || $3
128
+ end
129
+ end
130
+ def item.method_missing(name, *args) self[name] end
131
+ @items << item
132
+ end
133
+
134
+ end
135
+
136
+ def clean_content(tag, attrs, content)
137
+ content = content.to_s
138
+ case tag
139
+ when :pubDate, :lastBuildDate, :published, :updated, :expirationDate, :modified, :'dc:date'
140
+ Time.parse(content) rescue unescape(content)
141
+ when :author, :contributor, :skipHours, :skipDays
142
+ unescape(content.gsub(/<.*?>/,''))
143
+ else
144
+ content.empty? && "#{attrs} " =~ /href=['"]?([^'"]*)['" ]/mi ? $1.strip : unescape(content)
145
+ end
146
+ end
147
+
148
+ def clean_tag(tag)
149
+ tag.to_s.gsub(':','_').intern
150
+ end
151
+
152
+ def unescape(content)
153
+ if content =~ /([^-_.!~*'()a-zA-Z\d;\/?:@&=+$,\[\]]%)/u then
154
+ CGI.unescape(content).gsub(/(<!\[CDATA\[|\]\]>)/u,'').strip
155
+ else
156
+ content.gsub(/(<!\[CDATA\[|\]\]>)/u,'').strip
157
+ end
158
+ end
159
+ end
160
+
161
+ class SimpleRSSError < StandardError
162
+ end
@@ -0,0 +1,13 @@
1
+ Gem::Specification.new do |s|
2
+ s.name = "thoughtafter-simple-rss"
3
+ s.version = "1.2.3.1"
4
+ s.date = "2011-01-07"
5
+ s.summary = "A simple, flexible, extensible, and liberal RSS and Atom reader for Ruby. It is designed to be backwards compatible with the standard RSS parser, but will never do RSS generation."
6
+ s.email = "lucas@rufy.com"
7
+ s.homepage = "http://github.com/cardmagic/simple-rss"
8
+ s.description = "A simple, flexible, extensible, and liberal RSS and Atom reader for Ruby. It is designed to be backwards compatible with the standard RSS parser, but will never do RSS generation."
9
+ s.has_rdoc = true
10
+ s.authors = ["Lucas Carlson"]
11
+ s.files = ["install.rb", "lib", "lib/simple-rss.rb", "LICENSE", "Rakefile", "README.markdown", "simple-rss.gemspec", "test", "test/base", "test/base/base_test.rb", "test/data", "test/data/atom.xml", "test/data/not-rss.xml", "test/data/rss09.rdf", "test/data/rss20.xml", "test/test_helper.rb"]
12
+ s.rubyforge_project = 'simple-rss'
13
+ end
@@ -0,0 +1,72 @@
1
+ require File.dirname(__FILE__) + '/../test_helper'
2
+ class BaseTest < Test::Unit::TestCase
3
+ def setup
4
+ @rss09 = SimpleRSS.parse open(File.dirname(__FILE__) + '/../data/rss09.rdf')
5
+ @rss20 = SimpleRSS.parse open(File.dirname(__FILE__) + '/../data/rss20.xml')
6
+ @media_rss = SimpleRSS.parse open(File.dirname(__FILE__) + '/../data/media_rss.xml')
7
+ @atom = SimpleRSS.parse open(File.dirname(__FILE__) + '/../data/atom.xml')
8
+ end
9
+
10
+ def test_channel
11
+ assert_equal @rss09, @rss09.channel
12
+ assert_equal @rss20, @rss20.channel
13
+ assert_equal @atom, @atom.feed
14
+ end
15
+
16
+ def test_items
17
+ assert_kind_of Array, @rss09.items
18
+ assert_kind_of Array, @rss20.items
19
+ assert_kind_of Array, @atom.entries
20
+ end
21
+
22
+ def test_rss09
23
+ assert_equal 10, @rss09.items.size
24
+ assert_equal "Slashdot", @rss09.title
25
+ assert_equal "http://slashdot.org/", @rss09.channel.link
26
+ assert_equal "http://books.slashdot.org/article.pl?sid=05/08/29/1319236&amp;from=rss", @rss09.items.first.link
27
+ assert_equal "http://books.slashdot.org/article.pl?sid=05/08/29/1319236&amp;from=rss", @rss09.items.first[:link]
28
+ assert_equal Time.parse("Wed Aug 24 13:33:34 UTC 2005"), @rss20.items.first.pubDate
29
+ assert_equal Time.parse("Fri Sep 09 02:52:31 PDT 2005"), @rss09.channel.dc_date
30
+ end
31
+
32
+ def test_media_rss
33
+ assert_equal 20, @media_rss.items.size
34
+ assert_equal "Uploads from herval", @media_rss.title
35
+ assert_equal "http://www.flickr.com/photos/herval/", @media_rss.channel.link
36
+ assert_equal "http://www.flickr.com/photos/herval/4671960608/", @media_rss.items.first.link
37
+ assert_equal "http://www.flickr.com/photos/herval/4671960608/", @media_rss.items.first[:link]
38
+ assert_equal "http://farm5.static.flickr.com/4040/4671960608_10cb945d5c_o.jpg", @media_rss.items.first.media_content_url
39
+ assert_equal "image/jpeg", @media_rss.items.first.media_content_type
40
+ assert_equal "3168", @media_rss.items.first.media_content_height
41
+ assert_equal "4752", @media_rss.items.first.media_content_width
42
+ assert_equal "Woof?", @media_rss.items.first.media_title
43
+ assert_equal "http://farm5.static.flickr.com/4040/4671960608_954d2297bc_s.jpg", @media_rss.items.first.media_thumbnail_url
44
+ assert_equal "75", @media_rss.items.first.media_thumbnail_height
45
+ assert_equal "75", @media_rss.items.first.media_thumbnail_width
46
+ assert_equal "herval", @media_rss.items.first.media_credit
47
+ assert_equal "photographer", @media_rss.items.first.media_credit_role
48
+ assert_equal "pets frodo", @media_rss.items.first.media_category
49
+ assert_equal "urn:flickr:tags", @media_rss.items.first.media_category_scheme
50
+ end
51
+
52
+ def test_rss20
53
+ assert_equal 10, @rss20.items.size
54
+ assert_equal "Technoblog", @rss20.title
55
+ assert_equal "http://tech.rufy.com", @rss20.channel.link
56
+ assert_equal "http://feeds.feedburner.com/rufytech?m=68", @rss20.items.first.link
57
+ assert_equal "http://feeds.feedburner.com/rufytech?m=68", @rss20.items.first[:link]
58
+ assert_equal "This is an XML content feed. It is intended to be viewed in a newsreader or syndicated to another site.", @rss20.channel.feedburner_browserFriendly
59
+ end
60
+
61
+ def test_atom
62
+ assert_equal 1, @atom.entries.size
63
+ assert_equal "dive into mark", @atom.title
64
+ assert_equal "http://example.org/", @atom.feed.link
65
+ assert_equal "http://example.org/2005/04/02/atom", @atom.entries.first.link
66
+ assert_equal "http://example.org/2005/04/02/atom", @atom.entries.first[:link]
67
+ end
68
+
69
+ def test_bad_feed
70
+ assert_raise(SimpleRSSError) { SimpleRSS.parse(open(File.dirname(__FILE__) + '/../data/not-rss.xml')) }
71
+ end
72
+ end