zlainsw-simple-rss 1.2.3

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.
@@ -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