lifestream 0.1.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.
- data/README +0 -0
- data/Rakefile +77 -0
- data/VERSION +1 -0
- data/lib/lifestream.rb +55 -0
- data/lib/lifestream/branch.rb +11 -0
- data/lib/lifestream/channel.rb +55 -0
- data/lib/lifestream/channel/rss2.rb +26 -0
- data/lib/lifestream/request.rb +42 -0
- data/lib/lifestream/source.rb +13 -0
- data/lib/lifestream/stream.rb +20 -0
- data/test/lifestream.yml +9 -0
- data/test/lifestream/branch_test.rb +0 -0
- data/test/lifestream/channel_test.rb +0 -0
- data/test/lifestream/source_test.rb +0 -0
- data/test/lifestream/stream_test.rb +16 -0
- data/test/lifestream_test.rb +28 -0
- data/test/test_helper.rb +39 -0
- metadata +115 -0
data/README
ADDED
File without changes
|
data/Rakefile
ADDED
@@ -0,0 +1,77 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'rake'
|
3
|
+
|
4
|
+
require(File.join(File.dirname(__FILE__), 'lib', 'lifestream'))
|
5
|
+
|
6
|
+
begin
|
7
|
+
require 'jeweler'
|
8
|
+
Jeweler::Tasks.new do |gem|
|
9
|
+
gem.name = "lifestream"
|
10
|
+
gem.summary = %Q{downloads and parses lifestream information from sources like facebook and twitter}
|
11
|
+
gem.email = "adamhunter@me.com"
|
12
|
+
gem.homepage = "http://github.com/adamhunter/lifestream"
|
13
|
+
gem.authors = ["Adam Hunter"]
|
14
|
+
#gem.rubyforge_project = "lifestream"
|
15
|
+
gem.files = FileList["[A-Z]*", "{lib,test}/**/*"]
|
16
|
+
|
17
|
+
#gem.add_dependency('httparty', '0.4.3')
|
18
|
+
|
19
|
+
gem.add_development_dependency('thoughtbot-shoulda', '>= 2.10.1')
|
20
|
+
gem.add_development_dependency('jnunemaker-matchy', '0.4.0')
|
21
|
+
gem.add_development_dependency('mocha', '0.9.4')
|
22
|
+
gem.add_development_dependency('redgreen', '1.0.0')
|
23
|
+
end
|
24
|
+
|
25
|
+
Jeweler::RubyforgeTasks.new do |rubyforge|
|
26
|
+
rubyforge.doc_task = "rdoc"
|
27
|
+
end
|
28
|
+
rescue LoadError
|
29
|
+
puts "Jeweler not available. Install it with: sudo gem install technicalpickles-jeweler -s http://gems.github.com"
|
30
|
+
end
|
31
|
+
|
32
|
+
require 'rake/testtask'
|
33
|
+
Rake::TestTask.new(:test) do |test|
|
34
|
+
test.libs << 'lib' << 'test'
|
35
|
+
test.pattern = 'test/**/*_test.rb'
|
36
|
+
test.verbose = false
|
37
|
+
end
|
38
|
+
|
39
|
+
begin
|
40
|
+
require 'rcov/rcovtask'
|
41
|
+
Rcov::RcovTask.new do |test|
|
42
|
+
test.libs << 'test'
|
43
|
+
test.pattern = 'test/**/*_test.rb'
|
44
|
+
test.verbose = true
|
45
|
+
end
|
46
|
+
rescue LoadError
|
47
|
+
task :rcov do
|
48
|
+
abort "RCov is not available. In order to run rcov, you must: sudo gem install spicycode-rcov"
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
|
53
|
+
task :default => :test
|
54
|
+
|
55
|
+
require 'rake/rdoctask'
|
56
|
+
Rake::RDocTask.new do |rdoc|
|
57
|
+
if File.exist?('VERSION.yml')
|
58
|
+
config = YAML.load(File.read('VERSION.yml'))
|
59
|
+
version = "#{config[:major]}.#{config[:minor]}.#{config[:patch]}"
|
60
|
+
else
|
61
|
+
version = ""
|
62
|
+
end
|
63
|
+
|
64
|
+
rdoc.rdoc_dir = 'rdoc'
|
65
|
+
rdoc.title = "lifestream #{version}"
|
66
|
+
rdoc.rdoc_files.include('README*')
|
67
|
+
rdoc.rdoc_files.include('lib/**/*.rb')
|
68
|
+
end
|
69
|
+
|
70
|
+
namespace :lifestream do
|
71
|
+
|
72
|
+
desc "Generate lifestream.yml file"
|
73
|
+
task :configure do
|
74
|
+
|
75
|
+
end
|
76
|
+
|
77
|
+
end
|
data/VERSION
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
0.1.0
|
data/lib/lifestream.rb
ADDED
@@ -0,0 +1,55 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'erb'
|
3
|
+
|
4
|
+
module Lifestream
|
5
|
+
|
6
|
+
# Provides configurability to Lifestream. There are a number of options available, such as:
|
7
|
+
# * whiny: Will raise an error if Lifestream cannot process a given feed.
|
8
|
+
# Defaults to true
|
9
|
+
# * config: Path to the lifestream.yml file that contains the feeds to download.
|
10
|
+
# Defaults to gem location, you will probably want to set this
|
11
|
+
def self.options
|
12
|
+
@options ||= {
|
13
|
+
:config => File.join(File.expand_path(File.dirname(__FILE__)), '..', 'lifestream.yml'),
|
14
|
+
:whiny => true
|
15
|
+
}
|
16
|
+
end
|
17
|
+
|
18
|
+
# Generates a Lifestream::Stream which contains all the channels
|
19
|
+
# with their branches parsed from the feeds in the lifestream.yml file,
|
20
|
+
# ordered by their published_at date.
|
21
|
+
def self.run(preload = false)
|
22
|
+
lifestream = Lifestream::Stream.new
|
23
|
+
ensure_configuration_exists
|
24
|
+
|
25
|
+
feeds = YAML.load(File.open(options[:config]))
|
26
|
+
feeds.each_pair do |name, options|
|
27
|
+
klass = Lifestream::Channel.const_get(options['format'])
|
28
|
+
channel = klass.new(name, options['url'])
|
29
|
+
channel.branches if preload
|
30
|
+
lifestream.channels << channel
|
31
|
+
end
|
32
|
+
lifestream
|
33
|
+
end
|
34
|
+
|
35
|
+
protected
|
36
|
+
|
37
|
+
def self.ensure_configuration_exists
|
38
|
+
raise Lifestream::NoConfiguration.new("You must provide a configuration file") unless File.exists?(options[:config])
|
39
|
+
end
|
40
|
+
|
41
|
+
class NoConfiguration < StandardError; end
|
42
|
+
|
43
|
+
end
|
44
|
+
|
45
|
+
directory = File.expand_path(File.dirname(__FILE__))
|
46
|
+
|
47
|
+
# yes most of these are all named after parts of a stream
|
48
|
+
# http://en.wikipedia.org/wiki/Stream#Parts_of_a_stream
|
49
|
+
require File.join(directory, 'lifestream', 'branch')
|
50
|
+
require File.join(directory, 'lifestream', 'channel')
|
51
|
+
require File.join(directory, 'lifestream', 'channel', 'rss2')
|
52
|
+
require File.join(directory, 'lifestream', 'request')
|
53
|
+
require File.join(directory, 'lifestream', 'source')
|
54
|
+
require File.join(directory, 'lifestream', 'stream')
|
55
|
+
|
@@ -0,0 +1,55 @@
|
|
1
|
+
module Lifestream
|
2
|
+
class Channel
|
3
|
+
|
4
|
+
attr_accessor :name, :branches, :raw_text, :feed
|
5
|
+
|
6
|
+
def initialize name, url
|
7
|
+
@name, @branches, @request = name, [], Lifestream::Request.new(url)
|
8
|
+
end
|
9
|
+
|
10
|
+
def branches
|
11
|
+
create_branches if @branches.empty?
|
12
|
+
@branches
|
13
|
+
end
|
14
|
+
|
15
|
+
protected
|
16
|
+
|
17
|
+
def download
|
18
|
+
response = @request.get
|
19
|
+
raise unless response.kind_of?(Net::HTTPSuccess)
|
20
|
+
@raw_data = response.body
|
21
|
+
rescue => e
|
22
|
+
Lifestream::Channel::DownloadError.new("The URL #{@request.url} failed to download") if Lifestream.options[:whiny]
|
23
|
+
end
|
24
|
+
|
25
|
+
def create_branches
|
26
|
+
download
|
27
|
+
parse
|
28
|
+
feed_to_a.each do |item|
|
29
|
+
@branches << to_branch(item)
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
def parse
|
34
|
+
raise_method_error :parse
|
35
|
+
end
|
36
|
+
|
37
|
+
def feed_to_a
|
38
|
+
raise_method_error :feed_to_a
|
39
|
+
end
|
40
|
+
|
41
|
+
def to_branch
|
42
|
+
raise_method_error :to_branch
|
43
|
+
end
|
44
|
+
|
45
|
+
private
|
46
|
+
|
47
|
+
def raise_method_error method
|
48
|
+
raise Lifestream::Channel::MethodError, "Method `#{method}` must be overriden in the child class"
|
49
|
+
end
|
50
|
+
|
51
|
+
class MethodError < StandardError; end
|
52
|
+
class DownloadError < StandardError; end
|
53
|
+
|
54
|
+
end
|
55
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
require 'rss/1.0'
|
2
|
+
require 'rss/2.0'
|
3
|
+
|
4
|
+
module Lifestream
|
5
|
+
class Channel::Rss2 < Lifestream::Channel
|
6
|
+
|
7
|
+
protected
|
8
|
+
|
9
|
+
def parse
|
10
|
+
@feed = RSS::Parser.parse(@raw_data, false)
|
11
|
+
rescue => e
|
12
|
+
raise Lifestream::Channel::Rss2::MalformedFeed.new("The feed from #{@url} could not be parsed") if Lifestream.options[:whiny]
|
13
|
+
end
|
14
|
+
|
15
|
+
def feed_to_a
|
16
|
+
@feed.items || []
|
17
|
+
end
|
18
|
+
|
19
|
+
def to_branch(branch)
|
20
|
+
Lifestream::Branch.new(branch.pubDate, branch.title, branch.description)
|
21
|
+
end
|
22
|
+
|
23
|
+
class MalformedFeed < StandardError; end
|
24
|
+
|
25
|
+
end
|
26
|
+
end
|
@@ -0,0 +1,42 @@
|
|
1
|
+
module Lifestream
|
2
|
+
class Request
|
3
|
+
|
4
|
+
attr_accessor :url, :max_redirects
|
5
|
+
|
6
|
+
def initialize(url, options = {})
|
7
|
+
@url = url
|
8
|
+
options[:max_redirects] ||= 5
|
9
|
+
@max_redirects = options[:max_redirects]
|
10
|
+
end
|
11
|
+
|
12
|
+
def get(url = @url)
|
13
|
+
check_max_redirects
|
14
|
+
url, request = build_request(url)
|
15
|
+
response = Net::HTTP.start(url.host, url.port) do |http|
|
16
|
+
http.request(request)
|
17
|
+
end
|
18
|
+
if response.kind_of?(Net::HTTPRedirection)
|
19
|
+
@redirect += 1
|
20
|
+
get(response.header['location'])
|
21
|
+
else
|
22
|
+
response
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
protected
|
27
|
+
|
28
|
+
def build_request(url)
|
29
|
+
url = URI.parse(url)
|
30
|
+
request = Net::HTTP::Get.new("#{url.path}?#{url.query}", {'User-Agent' => 'Gecko'})
|
31
|
+
return url, request
|
32
|
+
end
|
33
|
+
|
34
|
+
def check_max_redirects
|
35
|
+
@redirect ||= 0
|
36
|
+
raise TooManyRedirects if @redirect > @max_redirects
|
37
|
+
end
|
38
|
+
|
39
|
+
class TooManyRedirects < StandardError; end
|
40
|
+
|
41
|
+
end
|
42
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
module Lifestream
|
2
|
+
class Stream
|
3
|
+
|
4
|
+
attr_accessor :channels
|
5
|
+
|
6
|
+
def initialize channels = []
|
7
|
+
@channels = channels
|
8
|
+
end
|
9
|
+
|
10
|
+
def branches
|
11
|
+
@branches ||= []
|
12
|
+
return @branches unless @branches.empty?
|
13
|
+
@channels.each do |channel|
|
14
|
+
channel.branches.each { |b| @branches << b }
|
15
|
+
end
|
16
|
+
@branches.sort { |a,b| a.published_at <=> b.published_at }.reverse
|
17
|
+
end
|
18
|
+
|
19
|
+
end
|
20
|
+
end
|
data/test/lifestream.yml
ADDED
@@ -0,0 +1,9 @@
|
|
1
|
+
facebook :
|
2
|
+
url : http://www.facebook.com/feeds/status.php?id=778750315&viewer=778750315&key=110b5987c2&format=rss20
|
3
|
+
format : Rss2
|
4
|
+
pandora :
|
5
|
+
url : http://feeds.pandora.com/feeds/people/ah125i/favorites.xml
|
6
|
+
format : Rss2
|
7
|
+
twitter :
|
8
|
+
url : http://twitter.com/statuses/user_timeline/15344521.rss
|
9
|
+
format : Rss2
|
File without changes
|
File without changes
|
File without changes
|
@@ -0,0 +1,16 @@
|
|
1
|
+
require 'test_helper'
|
2
|
+
|
3
|
+
class StreamTest < Test::Unit::TestCase
|
4
|
+
|
5
|
+
context "A Lifestream::Stream instance" do
|
6
|
+
|
7
|
+
setup { @lifestream = Lifestream.run }
|
8
|
+
|
9
|
+
should "return its branches in descending chronological order" do
|
10
|
+
@lifestream.branches.should_not be_nil
|
11
|
+
@lifestream.branches[0].published_at.should > @lifestream.branches[1].published_at
|
12
|
+
end
|
13
|
+
|
14
|
+
end
|
15
|
+
|
16
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
require 'test_helper'
|
2
|
+
|
3
|
+
class LifestreamTest < Test::Unit::TestCase
|
4
|
+
|
5
|
+
context "The Lifestream module" do
|
6
|
+
|
7
|
+
should "create a Lifestream::Stream instance with all of its channels and branches" do
|
8
|
+
@lifestream = Lifestream::run
|
9
|
+
@lifestream.channels.size.should == 3
|
10
|
+
@lifestream.channels.each { |c| c.should_not be_nil }
|
11
|
+
@lifestream.channels.each { |c| c.should be_a(Lifestream::Channel::Rss2) }
|
12
|
+
@lifestream.channels.each do |c|
|
13
|
+
c.branches.each { |b| b.should_not be_nil }
|
14
|
+
c.branches.each { |b| b.should be_a(Lifestream::Branch) }
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
should "set the configuration file to the provided path" do
|
19
|
+
original_path = Lifestream.options[:config]
|
20
|
+
test_config_path = 'some/config'
|
21
|
+
Lifestream.options[:config] = test_config_path
|
22
|
+
Lifestream.options[:config].should == test_config_path
|
23
|
+
Lifestream.options[:config] = original_path
|
24
|
+
end
|
25
|
+
|
26
|
+
end
|
27
|
+
|
28
|
+
end
|
data/test/test_helper.rb
ADDED
@@ -0,0 +1,39 @@
|
|
1
|
+
require 'test/unit'
|
2
|
+
require 'rubygems'
|
3
|
+
|
4
|
+
gem 'thoughtbot-shoulda', '>= 2.10.1'
|
5
|
+
gem 'jnunemaker-matchy', '0.4.0'
|
6
|
+
gem 'mocha', '0.9.7'
|
7
|
+
gem 'redgreen', '>= 1.0.0'
|
8
|
+
|
9
|
+
require 'shoulda'
|
10
|
+
require 'matchy'
|
11
|
+
require 'mocha'
|
12
|
+
require 'fakeweb'
|
13
|
+
require 'redgreen' unless ENV['TM_FILENAME']
|
14
|
+
|
15
|
+
directory = File.expand_path(File.dirname(__FILE__))
|
16
|
+
|
17
|
+
require File.join(directory, '..', 'lib', 'lifestream')
|
18
|
+
|
19
|
+
Lifestream.options[:config] = 'lifestream.yml'
|
20
|
+
|
21
|
+
class Test::Unit::TestCase
|
22
|
+
|
23
|
+
custom_matcher :be_nil do |receiver, matcher, args|
|
24
|
+
matcher.positive_failure_message = "Expected #{receiver} to be nil but it wasn't"
|
25
|
+
matcher.negative_failure_message = "Expected #{receiver} not to be nil but it was"
|
26
|
+
receiver.nil?
|
27
|
+
end
|
28
|
+
|
29
|
+
custom_matcher :be_a do |receiver, matcher, args|
|
30
|
+
receiver.class == args[0]
|
31
|
+
end
|
32
|
+
|
33
|
+
custom_matcher :be_empty do |receiver, matcher, args|
|
34
|
+
matcher.positive_failure_message = "Expected #{receiver} to be empty but it wasn't"
|
35
|
+
matcher.negative_failure_message = "Expected #{receiver} not to be empty but it was"
|
36
|
+
receiver.empty?
|
37
|
+
end
|
38
|
+
|
39
|
+
end
|
metadata
ADDED
@@ -0,0 +1,115 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: lifestream
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Adam Hunter
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
|
12
|
+
date: 2009-10-03 00:00:00 -04:00
|
13
|
+
default_executable:
|
14
|
+
dependencies:
|
15
|
+
- !ruby/object:Gem::Dependency
|
16
|
+
name: thoughtbot-shoulda
|
17
|
+
type: :development
|
18
|
+
version_requirement:
|
19
|
+
version_requirements: !ruby/object:Gem::Requirement
|
20
|
+
requirements:
|
21
|
+
- - ">="
|
22
|
+
- !ruby/object:Gem::Version
|
23
|
+
version: 2.10.1
|
24
|
+
version:
|
25
|
+
- !ruby/object:Gem::Dependency
|
26
|
+
name: jnunemaker-matchy
|
27
|
+
type: :development
|
28
|
+
version_requirement:
|
29
|
+
version_requirements: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - "="
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: 0.4.0
|
34
|
+
version:
|
35
|
+
- !ruby/object:Gem::Dependency
|
36
|
+
name: mocha
|
37
|
+
type: :development
|
38
|
+
version_requirement:
|
39
|
+
version_requirements: !ruby/object:Gem::Requirement
|
40
|
+
requirements:
|
41
|
+
- - "="
|
42
|
+
- !ruby/object:Gem::Version
|
43
|
+
version: 0.9.4
|
44
|
+
version:
|
45
|
+
- !ruby/object:Gem::Dependency
|
46
|
+
name: redgreen
|
47
|
+
type: :development
|
48
|
+
version_requirement:
|
49
|
+
version_requirements: !ruby/object:Gem::Requirement
|
50
|
+
requirements:
|
51
|
+
- - "="
|
52
|
+
- !ruby/object:Gem::Version
|
53
|
+
version: 1.0.0
|
54
|
+
version:
|
55
|
+
description:
|
56
|
+
email: adamhunter@me.com
|
57
|
+
executables: []
|
58
|
+
|
59
|
+
extensions: []
|
60
|
+
|
61
|
+
extra_rdoc_files:
|
62
|
+
- README
|
63
|
+
files:
|
64
|
+
- README
|
65
|
+
- Rakefile
|
66
|
+
- VERSION
|
67
|
+
- lib/lifestream.rb
|
68
|
+
- lib/lifestream/branch.rb
|
69
|
+
- lib/lifestream/channel.rb
|
70
|
+
- lib/lifestream/channel/rss2.rb
|
71
|
+
- lib/lifestream/request.rb
|
72
|
+
- lib/lifestream/source.rb
|
73
|
+
- lib/lifestream/stream.rb
|
74
|
+
- test/lifestream.yml
|
75
|
+
- test/lifestream/branch_test.rb
|
76
|
+
- test/lifestream/channel_test.rb
|
77
|
+
- test/lifestream/source_test.rb
|
78
|
+
- test/lifestream/stream_test.rb
|
79
|
+
- test/lifestream_test.rb
|
80
|
+
- test/test_helper.rb
|
81
|
+
has_rdoc: true
|
82
|
+
homepage: http://github.com/adamhunter/lifestream
|
83
|
+
licenses: []
|
84
|
+
|
85
|
+
post_install_message:
|
86
|
+
rdoc_options:
|
87
|
+
- --charset=UTF-8
|
88
|
+
require_paths:
|
89
|
+
- lib
|
90
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
91
|
+
requirements:
|
92
|
+
- - ">="
|
93
|
+
- !ruby/object:Gem::Version
|
94
|
+
version: "0"
|
95
|
+
version:
|
96
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
97
|
+
requirements:
|
98
|
+
- - ">="
|
99
|
+
- !ruby/object:Gem::Version
|
100
|
+
version: "0"
|
101
|
+
version:
|
102
|
+
requirements: []
|
103
|
+
|
104
|
+
rubyforge_project:
|
105
|
+
rubygems_version: 1.3.5
|
106
|
+
signing_key:
|
107
|
+
specification_version: 3
|
108
|
+
summary: downloads and parses lifestream information from sources like facebook and twitter
|
109
|
+
test_files:
|
110
|
+
- test/lifestream/branch_test.rb
|
111
|
+
- test/lifestream/channel_test.rb
|
112
|
+
- test/lifestream/source_test.rb
|
113
|
+
- test/lifestream/stream_test.rb
|
114
|
+
- test/lifestream_test.rb
|
115
|
+
- test/test_helper.rb
|