zlainsw-simple-rss 1.2.3

Sign up to get free protection for your applications and to get access to all the features.
@@ -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
@@ -0,0 +1,40 @@
1
+ require 'rbconfig'
2
+ require 'find'
3
+ require 'ftools'
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
+ }
@@ -0,0 +1,153 @@
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
+ ]
35
+
36
+ def initialize(source, options={})
37
+ @source = source.respond_to?(:read) ? source.read : source.to_s
38
+ @items = Array.new
39
+ @options = Hash.new.update(options)
40
+
41
+ parse
42
+ end
43
+
44
+ def channel() self end
45
+ alias :feed :channel
46
+
47
+ class << self
48
+ def feed_tags
49
+ @@feed_tags
50
+ end
51
+ def feed_tags=(ft)
52
+ @@feed_tags = ft
53
+ end
54
+
55
+ def item_tags
56
+ @@item_tags
57
+ end
58
+ def item_tags=(it)
59
+ @@item_tags = it
60
+ end
61
+
62
+ # The strict attribute is for compatibility with Ruby's standard RSS parser
63
+ def parse(source, options={})
64
+ new source, options
65
+ end
66
+ end
67
+
68
+ private
69
+
70
+ def parse
71
+ raise SimpleRSSError, "Poorly formatted feed" unless @source =~ %r{<(channel|feed).*?>.*?</(channel|feed)>}mi
72
+
73
+ # Feed's title and link
74
+ feed_content = $1 if @source =~ %r{(.*?)<(rss:|atom:)?(item|entry).*?>.*?</(rss:|atom:)?(item|entry)>}mi
75
+
76
+ @@feed_tags.each do |tag|
77
+ if feed_content && feed_content =~ %r{<(rss:|atom:)?#{tag}(.*?)>(.*?)</(rss:|atom:)?#{tag}>}mi
78
+ nil
79
+ elsif feed_content && feed_content =~ %r{<(rss:|atom:)?#{tag}(.*?)\/\s*>}mi
80
+ nil
81
+ elsif @source =~ %r{<(rss:|atom:)?#{tag}(.*?)>(.*?)</(rss:|atom:)?#{tag}>}mi
82
+ nil
83
+ elsif @source =~ %r{<(rss:|atom:)?#{tag}(.*?)\/\s*>}mi
84
+ nil
85
+ end
86
+
87
+ if $2 || $3
88
+ tag_cleaned = clean_tag(tag)
89
+ instance_variable_set("@#{ tag_cleaned }", clean_content(tag, $2, $3))
90
+ self.class.send(:attr_reader, tag_cleaned)
91
+ end
92
+ end
93
+
94
+ # RSS items' title, link, and description
95
+ @source.scan( %r{<(rss:|atom:)?(item|entry)([\s][^>]*)?>(.*?)</(rss:|atom:)?(item|entry)>}mi ) do |match|
96
+ item = Hash.new
97
+ @@item_tags.each do |tag|
98
+ if tag.to_s.include?("+")
99
+ tag_data = tag.to_s.split("+")
100
+ tag = tag_data[0]
101
+ rel = tag_data[1]
102
+
103
+ if match[3] =~ %r{<(rss:|atom:)?#{tag}(.*?)rel=['"]#{rel}['"](.*?)>(.*?)</(rss:|atom:)?#{tag}>}mi
104
+ nil
105
+ elsif match[3] =~ %r{<(rss:|atom:)?#{tag}(.*?)rel=['"]#{rel}['"](.*?)/\s*>}mi
106
+ nil
107
+ end
108
+ item[clean_tag("#{tag}+#{rel}")] = clean_content(tag, $3, $4) if $3 || $4
109
+ else
110
+ if match[3] =~ %r{<(rss:|atom:)?#{tag}(.*?)>(.*?)</(rss:|atom:)?#{tag}>}mi
111
+ nil
112
+ elsif match[3] =~ %r{<(rss:|atom:)?#{tag}(.*?)/\s*>}mi
113
+ nil
114
+ end
115
+ item[clean_tag(tag)] = clean_content(tag, $2, $3) if $2 || $3
116
+ end
117
+ end
118
+ if item[:pubDate]
119
+ item[:published] = item[:pubDate]
120
+ end
121
+ def item.method_missing(name, *args) self[name] end
122
+ @items << item
123
+ end
124
+
125
+ end
126
+
127
+ def clean_content(tag, attrs, content)
128
+ content = content.to_s
129
+ case tag
130
+ when :pubDate, :lastBuildDate, :published, :updated, :expirationDate, :modified, :'dc:date'
131
+ Time.parse(content) rescue unescape(content)
132
+ when :author, :contributor, :skipHours, :skipDays
133
+ unescape(content.gsub(/<.*?>/,''))
134
+ else
135
+ content.empty? && "#{attrs} " =~ /href=['"]?([^'"]*)['" ]/mi ? $1.strip : unescape(content)
136
+ end
137
+ end
138
+
139
+ def clean_tag(tag)
140
+ tag.to_s.gsub(':','_').intern
141
+ end
142
+
143
+ def unescape(content)
144
+ if content =~ /([^-_.!~*'()a-zA-Z\d;\/?:@&=+$,\[\]]%)/n then
145
+ CGI.unescape(content).gsub(/(<!\[CDATA\[|\]\]>)/,'').strip
146
+ else
147
+ content.gsub(/(<!\[CDATA\[|\]\]>)/,'').strip
148
+ end
149
+ end
150
+ end
151
+
152
+ class SimpleRSSError < StandardError
153
+ end
@@ -0,0 +1,12 @@
1
+ Gem::Specification.new do |s|
2
+ s.name = "zlainsw-simple-rss"
3
+ s.version = "1.2.3"
4
+ s.date = "2009-12-24"
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 = "zakainsworth@gmail.com"
7
+ s.homepage = "http://github.com/zlainsw/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", "Zak Ainsworth"]
11
+ s.files = ["install.rb", "lib", "lib/simple-rss.rb", "LICENSE", "Rakefile", "README", "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
+ end
@@ -0,0 +1,51 @@
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
+ @atom = SimpleRSS.parse open(File.dirname(__FILE__) + '/../data/atom.xml')
7
+ end
8
+
9
+ def test_channel
10
+ assert_equal @rss09, @rss09.channel
11
+ assert_equal @rss20, @rss20.channel
12
+ assert_equal @atom, @atom.feed
13
+ end
14
+
15
+ def test_items
16
+ assert_kind_of Array, @rss09.items
17
+ assert_kind_of Array, @rss20.items
18
+ assert_kind_of Array, @atom.entries
19
+ end
20
+
21
+ def test_rss09
22
+ assert_equal 10, @rss09.items.size
23
+ assert_equal "Slashdot", @rss09.title
24
+ assert_equal "http://slashdot.org/", @rss09.channel.link
25
+ assert_equal "http://books.slashdot.org/article.pl?sid=05/08/29/1319236&amp;from=rss", @rss09.items.first.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 Time.parse("Wed Aug 24 13:33:34 UTC 2005"), @rss20.items.first.pubDate
28
+ assert_equal Time.parse("Fri Sep 09 02:52:31 PDT 2005"), @rss09.channel.dc_date
29
+ end
30
+
31
+ def test_rss20
32
+ assert_equal 10, @rss20.items.size
33
+ assert_equal "Technoblog", @rss20.title
34
+ assert_equal "http://tech.rufy.com", @rss20.channel.link
35
+ assert_equal "http://feeds.feedburner.com/rufytech?m=68", @rss20.items.first.link
36
+ assert_equal "http://feeds.feedburner.com/rufytech?m=68", @rss20.items.first[:link]
37
+ 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
38
+ end
39
+
40
+ def test_atom
41
+ assert_equal 1, @atom.entries.size
42
+ assert_equal "dive into mark", @atom.title
43
+ assert_equal "http://example.org/", @atom.feed.link
44
+ assert_equal "http://example.org/2005/04/02/atom", @atom.entries.first.link
45
+ assert_equal "http://example.org/2005/04/02/atom", @atom.entries.first[:link]
46
+ end
47
+
48
+ def test_bad_feed
49
+ assert_raise(SimpleRSSError) { SimpleRSS.parse(open(File.dirname(__FILE__) + '/../data/not-rss.xml')) }
50
+ end
51
+ end