twittertype 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
data/README.rdoc ADDED
@@ -0,0 +1,36 @@
1
+ = Overview
2
+ TwitterType is a basic utility that analyses a Twitter user's tweets and
3
+ determine what type of Tweeter the user is. The set of types identified are:
4
+
5
+ [:+chatter+] most tweets are in response to tweets by other Twitter users
6
+ [:+linker+] most tweets include links to URLs
7
+ [:+retweeter+] most tweets are forwarding on other people's tweets
8
+ [:+originator+] most tweets are original content, not containing links, retweets or responses
9
+ [:+unknown_inconclusive+] no strong trends to base type on.
10
+ [:+unknown_silent+] no tweets found in last 7 days
11
+ [:+unknown_protected_user+] user has not granted public access to tweets
12
+
13
+ = Usage
14
+
15
+ Simply run the twitter_type.rb file passing the Twitter screen name of the person you wish to analyse.
16
+ The analysis of this persons most tweets from the last 7 days will be displayed.
17
+
18
+ For example:
19
+
20
+ ruby twitter_type.rb APlusK
21
+ APlusK: type :linker, #tweets 20, #replies 1, #retweets 5, #links 7
22
+
23
+ To analyse multiple Twitter user's, simply pass each on the command line. For example:
24
+
25
+ ruby twitter_type.rb APlusK BillGates EvaLongoria
26
+ APlusK: type :originator, #tweets 20, #replies 1, #retweets 5, #links 6
27
+ BillGates: type :linker, #tweets 19, #replies 1, #retweets 2, #links 11
28
+ EvaLongoria: type :linker, #tweets 19, #replies 3, #retweets 1, #links 11
29
+
30
+ = Notes
31
+
32
+ * The Twitter screen name parameter is case insensitive.
33
+ * Each tweet will only be classified as one of a link, retweet, reply or original tweet. For example, a retweet containing a link is just a retweet and not both.
34
+
35
+ = Copyright
36
+ Copyright (c) 2010 Andy Marks, released under the MIT license
@@ -0,0 +1,25 @@
1
+ require 'tweeterprofile'
2
+
3
+ module TwitterType
4
+
5
+ class ProfileFactory
6
+
7
+ def initialize(user)
8
+ raise ArgumentError.new("No user name supplied") if user.nil? or !user.is_a?(String)
9
+ @user = user
10
+ end
11
+
12
+ def build(tweets)
13
+ raise ArgumentError.new("No tweets to analyse") if tweets.nil?
14
+ raise ArgumentError.new("Tweets are not enumerable") if !tweets.is_a?(Enumerable)
15
+
16
+ profile = TweeterProfile.new(@user)
17
+ tweets.each do |tweet|
18
+ profile.update_from(tweet)
19
+ end
20
+
21
+ return profile
22
+ end
23
+ end
24
+
25
+ end
@@ -0,0 +1,84 @@
1
+ module TwitterType
2
+
3
+ class TweeterProfile
4
+ attr_accessor :screen_name,
5
+ :tweet_count,
6
+ :reply_count,
7
+ :retweet_count,
8
+ :link_count,
9
+ :inferred_type
10
+
11
+ def initialize(screen_name)
12
+ @tweet_count = 0
13
+ @reply_count = 0
14
+ @retweet_count = 0
15
+ @link_count = 0
16
+ @screen_name = screen_name
17
+ end
18
+
19
+ def update_from(tweet)
20
+ begin
21
+ @tweet_count = @tweet_count + 1
22
+
23
+ if tweet.text.slice(0, 1) == '@'
24
+ @reply_count = @reply_count + 1
25
+ return
26
+ end
27
+
28
+ @retweet_count = @retweet_count + 1 if tweet.text.slice(0, 2) == 'RT'
29
+ @link_count = @link_count + 1 if tweet.text.index('http://') != nil
30
+ rescue NoMethodError => root
31
+ raise ArgumentError.new("Missing method responses in tweet structure:" + root.to_s)
32
+ end
33
+ end
34
+
35
+ def to_s
36
+ type_to_s = (@inferred_type ? "type " + @inferred_type.inspect + ", " : "")
37
+ @screen_name + ": " + type_to_s + "#tweets " + @tweet_count.to_s + ", #replies " + @reply_count.to_s + ", #retweets " + @retweet_count.to_s + ", #links " + @link_count.to_s + "\n"
38
+ end
39
+
40
+ def ==(other)
41
+ result = @screen_name == other.screen_name
42
+ result = result && @tweet_count == other.tweet_count
43
+ result = result && @reply_count == other.reply_count
44
+ result = result && @retweet_count == other.retweet_count
45
+ result = result && @link_count == other.link_count
46
+
47
+ return result
48
+ end
49
+
50
+ def infer_type
51
+
52
+ attributes = [@retweet_count, @link_count, @reply_count]
53
+ highest_count = attributes.max
54
+
55
+ raise ArgumentError if @tweet_count < highest_count
56
+
57
+ @inferred_type = set_type(highest_count)
58
+
59
+ return @inferred_type
60
+ end
61
+
62
+ private
63
+ def set_type(highest_count)
64
+ return :unknown_silent if @tweet_count == 0
65
+
66
+ non_original_tweet_count = @retweet_count + @link_count + @reply_count
67
+ original_tweets = @tweet_count - non_original_tweet_count
68
+ return :originator if original_tweets > highest_count
69
+
70
+ return :unknown_inconclusive if equal_highest?(highest_count, @retweet_count, @link_count)
71
+ return :unknown_inconclusive if equal_highest?(highest_count, @retweet_count, @reply_count)
72
+ return :unknown_inconclusive if equal_highest?(highest_count, @link_count, @reply_count)
73
+
74
+ return :retweeter if highest_count == @retweet_count
75
+ return :linker if highest_count == @link_count
76
+ return :chatter if highest_count == @reply_count
77
+ end
78
+
79
+ def equal_highest?(highest_count, first, second)
80
+ highest_count == first and highest_count == second
81
+ end
82
+
83
+ end
84
+ end
@@ -0,0 +1,11 @@
1
+ require 'twittertypeinferrer'
2
+
3
+ module TwitterType
4
+
5
+ inferrer = TwitterType::TypeInferrer.new()
6
+
7
+ ARGV.each do |screen_name|
8
+ inferrer.infer(screen_name)
9
+ print inferrer.profile
10
+ end
11
+ end
@@ -0,0 +1,13 @@
1
+ require 'rubygems'
2
+ require 'twitter'
3
+
4
+ module TwitterType
5
+
6
+ class TwitterClient
7
+ def gather_recent_tweets_for (screen_name)
8
+ raise ArgumentError if screen_name.nil? or screen_name.strip.size == 0
9
+
10
+ Twitter::Client.new.timeline_for(:user, :id => screen_name)
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,39 @@
1
+ require 'rubygems'
2
+ require 'twitterclient'
3
+
4
+ module TwitterType
5
+
6
+ class TwitterClientWrapper < TwitterClient
7
+
8
+ attr_reader :client
9
+
10
+ def initialize(client = TwitterClient.new)
11
+ @client = client
12
+ end
13
+
14
+ def gather_recent_tweets_for (screen_name)
15
+ begin
16
+ @client.gather_recent_tweets_for(screen_name)
17
+ rescue Twitter::RESTError => error
18
+ raise ProtectedUserAccessError.new(error.to_s) if ProtectedUserAccessError.fits(error)
19
+ raise TwitterClientError.new(error.to_s)
20
+ end
21
+ end
22
+ end
23
+
24
+ class TwitterClientError < StandardError
25
+ attr :message
26
+
27
+ def initialize(message)
28
+ @message = message
29
+ end
30
+ end
31
+
32
+ class ProtectedUserAccessError < TwitterClientError
33
+ CODE = "401"
34
+
35
+ def self.fits(error)
36
+ return !error.code.index(CODE).nil?
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,29 @@
1
+ require "twitterclientwrapper"
2
+ require "profilefactory"
3
+
4
+ module TwitterType
5
+
6
+ class TypeInferrer
7
+ attr_writer :client
8
+ attr_reader :profile
9
+
10
+ def initialize()
11
+ @client = TwitterClientWrapper.new
12
+ end
13
+
14
+ def infer(user)
15
+ begin
16
+ tweets = @client.gather_recent_tweets_for(user)
17
+ @profile = ProfileFactory.new(user).build(tweets)
18
+ @profile.infer_type
19
+ rescue TwitterType::ProtectedUserAccessError => error
20
+ @profile = ProfileFactory.new(user).build([])
21
+ @profile.inferred_type = :unknown_protected_user
22
+ end
23
+
24
+ self
25
+ end
26
+
27
+ end
28
+
29
+ end
@@ -0,0 +1,12 @@
1
+ require 'spec'
2
+ require 'twittertypeinferrer'
3
+ require 'twitterclient'
4
+
5
+ include TwitterType
6
+
7
+ describe TypeInferrer do
8
+
9
+ it "should pass a basic smoke test" do
10
+ TypeInferrer.new.infer("andee_marks")
11
+ end
12
+ end
@@ -0,0 +1,56 @@
1
+ require "spec"
2
+ require "profilefactory"
3
+ require 'tweeterprofile'
4
+
5
+ describe TwitterType::ProfileFactory do
6
+ before(:each) do
7
+ @empty_profile = TweeterProfile.new("user")
8
+ @factory = TwitterType::ProfileFactory.new(@empty_profile.screen_name)
9
+ end
10
+
11
+ def setup_valid_fields_in_tweet
12
+ mock_valid_tweet = mock()
13
+ mock_valid_tweet.stub!(:to_user).and_return("user")
14
+ mock_valid_tweet.stub!(:text).and_return("RT textm http://www.cool.com")
15
+ mock_valid_tweet.stub!(:id).and_return(12345678)
16
+
17
+ return mock_valid_tweet
18
+ end
19
+
20
+ it "should fail construction without a user" do
21
+ lambda {ProfileFactory.new}.should raise_error(ArgumentError)
22
+ lambda {ProfileFactory.new(nil)}.should raise_error(ArgumentError)
23
+ lambda {ProfileFactory.new(1)}.should raise_error(ArgumentError)
24
+
25
+ ProfileFactory.new("user")
26
+ end
27
+
28
+ it "should fail if it cannot enumerate tweets" do
29
+ lambda {@factory.build()}.should raise_error(ArgumentError)
30
+ lambda {@factory.build(nil)}.should raise_error(ArgumentError)
31
+ lambda {@factory.build("tweets")}.should raise_error(ArgumentError)
32
+ lambda {@factory.build([1, 2, 3])}.should raise_error(ArgumentError)
33
+
34
+ @factory.build(Array.new())
35
+ end
36
+
37
+ it "should return an empty profile if no tweets are supplied" do
38
+ @factory.build(Array.new()).should == @empty_profile
39
+ end
40
+
41
+ it "should take a tweet with a valid set of fields" do
42
+ lambda {@factory.build(Array.new(1, "tweet"))}.should raise_error(ArgumentError)
43
+
44
+ mock_valid_tweet = setup_valid_fields_in_tweet()
45
+
46
+ @factory.build(Array.new(1, mock_valid_tweet))
47
+ end
48
+
49
+ it "needs the tweet text to be a string" do
50
+ tweet_with_non_string_text = mock()
51
+ tweet_with_non_string_text.stub!(:to_user).and_return(nil)
52
+ tweet_with_non_string_text.stub!(:text).and_return(1)
53
+
54
+ lambda {@factory.build(Array.new(1, tweet_with_non_string_text))}.should raise_error(ArgumentError)
55
+ end
56
+ end
@@ -0,0 +1,177 @@
1
+ require 'spec'
2
+ require 'tweeterprofile'
3
+
4
+ describe TwitterType::TweeterProfile, " before update" do
5
+
6
+ before(:each) do
7
+ @profile = TweeterProfile.new("andy")
8
+ end
9
+
10
+ it "should provide a valid string representation for a newly created profile" do
11
+ TweeterProfile.new("andy").to_s.should eql("andy: #tweets 0, #replies 0, #retweets 0, #links 0\n")
12
+ end
13
+
14
+ it "should be equal to another profile with the same values" do
15
+ first = TweeterProfile.new("user")
16
+ first.link_count = 1
17
+ first.retweet_count = 2
18
+ first.reply_count = 3
19
+ first.tweet_count = 4
20
+
21
+ second = TweeterProfile.new(first.screen_name)
22
+ second.link_count = first.link_count
23
+ second.retweet_count = first.retweet_count
24
+ second.reply_count = first.reply_count
25
+ second.tweet_count = first.tweet_count
26
+
27
+ first.equal?(second).should == false
28
+ (first == second).should == true
29
+ end
30
+
31
+ it "should provide a valid string representation for an updated profile" do
32
+ @profile.tweet_count = 1
33
+ @profile.reply_count = 2
34
+ @profile.retweet_count = 3
35
+ @profile.link_count = 4
36
+
37
+ @profile.to_s.should eql("andy: #tweets 1, #replies 2, #retweets 3, #links 4\n")
38
+
39
+ end
40
+
41
+ end
42
+
43
+ describe TwitterType::TweeterProfile, " being updated" do
44
+
45
+ before(:each) do
46
+ @profile = TweeterProfile.new("andy")
47
+ end
48
+
49
+ Tweet = Struct.new(:text, :to_user)
50
+
51
+ it "should fail when asked to update itself from a nil tweet" do
52
+ lambda {@profile.update_from(nil)}.should raise_error(ArgumentError)
53
+ end
54
+
55
+ it "should increase the tweet count for each tweet found" do
56
+ @profile.tweet_count.should == 0
57
+
58
+ @profile.update_from(Tweet.new("text"))
59
+ @profile.tweet_count.should == 1
60
+
61
+ @profile.update_from(Tweet.new("text"))
62
+ @profile.tweet_count.should == 2
63
+
64
+ end
65
+
66
+ it "should increase the reply count for each tweet sent to a user" do
67
+ @profile.reply_count.should == 0
68
+
69
+ @profile.update_from(Tweet.new("@user text"))
70
+ @profile.reply_count.should == 1
71
+
72
+ @profile.update_from(Tweet.new("@user text"))
73
+ @profile.reply_count.should == 2
74
+
75
+ @profile.update_from(Tweet.new("text"))
76
+ @profile.reply_count.should == 2
77
+ end
78
+
79
+ it "should let a reply take precedence over a retweet" do
80
+ @profile.reply_count.should == 0
81
+ @profile.update_from(Tweet.new("@user text"))
82
+ @profile.reply_count.should == 1
83
+ @profile.retweet_count.should == 0
84
+ end
85
+
86
+ it "should increase the retweet count for each tweet forwarded from a user" do
87
+ @profile.retweet_count.should == 0
88
+
89
+ @profile.update_from(Tweet.new("RT text"))
90
+ @profile.retweet_count.should == 1
91
+
92
+ @profile.update_from(Tweet.new("RT text"))
93
+ @profile.retweet_count.should == 2
94
+
95
+ @profile.update_from(Tweet.new("text"))
96
+ @profile.retweet_count.should == 2
97
+ end
98
+
99
+ it "should increase the link count for each tweet mentioning an URL" do
100
+ @profile.link_count.should == 0
101
+
102
+ @profile.update_from(Tweet.new("text http://www.twitter.com", nil))
103
+ @profile.link_count.should == 1
104
+
105
+ @profile.update_from(Tweet.new("text http://www.twitter.com", nil))
106
+ @profile.link_count.should == 2
107
+
108
+ @profile.update_from(Tweet.new("text"))
109
+ @profile.link_count.should == 2
110
+ end
111
+
112
+ end
113
+
114
+ describe TwitterType::TweeterProfile, " inferring a type" do
115
+ before(:each) do
116
+ @cut = TweeterProfile.new("user")
117
+ end
118
+
119
+ it "should fail to infer a profile which has less tweets than possible given other attributes" do
120
+ setup_profile({:retweet_count => 1, :link_count => 0, :reply_count => 0, :tweet_count => 0})
121
+ lambda{@cut.infer_type}.should raise_error(ArgumentError)
122
+
123
+ setup_profile({:retweet_count => 0, :link_count => 1, :reply_count => 0, :tweet_count => 0})
124
+ lambda{@cut.infer_type}.should raise_error(ArgumentError)
125
+
126
+ setup_profile({:retweet_count => 0, :link_count => 0, :reply_count => 1, :tweet_count => 0})
127
+ lambda{@cut.infer_type}.should raise_error(ArgumentError)
128
+ end
129
+
130
+ it "should infer retweeter from a profile with predominantly retweets" do
131
+ setup_profile({:retweet_count => 1, :link_count => 0, :reply_count => 0, :tweet_count => 1})
132
+ @cut.infer_type.should == :retweeter
133
+ end
134
+
135
+ it "should included the inferred type in the string representation" do
136
+ setup_profile({:retweet_count => 1, :link_count => 0, :reply_count => 0, :tweet_count => 1})
137
+ @cut.infer_type
138
+ @cut.to_s.should eql("user: type :retweeter, #tweets 1, #replies 0, #retweets 1, #links 0\n")
139
+ end
140
+
141
+ it "should infer linker from a profile with only links" do
142
+ setup_profile({:retweet_count => 0, :link_count => 1, :reply_count => 0, :tweet_count => 1})
143
+ @cut.infer_type.should == :linker
144
+ end
145
+
146
+ it "should infer linker from a profile with predominantly links" do
147
+ setup_profile({:retweet_count => 0, :link_count => 15, :reply_count => 0, :tweet_count => 20})
148
+ @cut.infer_type.should == :linker
149
+ end
150
+
151
+ it "should infer chatter from a profile with predominantly replies" do
152
+ setup_profile({:retweet_count => 0, :link_count => 0, :reply_count => 1, :tweet_count => 1})
153
+ @cut.infer_type.should == :chatter
154
+ end
155
+
156
+ it "should infer originator from a profile with predominantly original tweets" do
157
+ setup_profile({:retweet_count => 1, :link_count => 1, :reply_count => 1, :tweet_count => 10})
158
+ @cut.infer_type.should == :originator
159
+ end
160
+
161
+ it "should infer no clear type from a profile with no clear trends" do
162
+ setup_profile({:retweet_count => 1, :link_count => 1, :reply_count => 1, :tweet_count => 3})
163
+ @cut.infer_type.should == :unknown_inconclusive
164
+ end
165
+
166
+ it "should infer no clear type from an empty profile" do
167
+ setup_profile({:retweet_count => 0, :link_count => 0, :reply_count => 0, :tweet_count => 0})
168
+ @cut.infer_type.should == :unknown_silent
169
+ end
170
+
171
+ def setup_profile(return_values)
172
+ @cut.retweet_count = return_values[:retweet_count]
173
+ @cut.link_count = return_values[:link_count]
174
+ @cut.reply_count = return_values[:reply_count]
175
+ @cut.tweet_count = return_values[:tweet_count]
176
+ end
177
+ end
@@ -0,0 +1,12 @@
1
+ require "spec"
2
+ require "twitterclient"
3
+
4
+ describe TwitterType::TwitterClient do
5
+
6
+ it "should fail with an empty or nil user name" do
7
+ lambda{TwitterClient.new.gather_recent_tweets_for(nil)}.should raise_error(ArgumentError)
8
+ lambda{TwitterClient.new.gather_recent_tweets_for("")}.should raise_error(ArgumentError)
9
+ lambda{TwitterClient.new.gather_recent_tweets_for(" ")}.should raise_error(ArgumentError)
10
+ lambda{TwitterClient.new.gather_recent_tweets_for(' ')}.should raise_error(ArgumentError)
11
+ end
12
+ end
@@ -0,0 +1,37 @@
1
+ require "spec"
2
+ require "twitterclientwrapper"
3
+
4
+ describe TwitterType::TwitterClientWrapper do
5
+
6
+ it "should subclass TwitterClient" do
7
+ TwitterClientWrapper.superclass.should == TwitterClient
8
+ end
9
+
10
+ it "should allow injection of a TwitterClient" do
11
+ TwitterClientWrapper.new(TwitterClient.new)
12
+ end
13
+
14
+ it "should default the client to a TwitterClient instance" do
15
+ TwitterClientWrapper.new.client.class.should == TwitterClient
16
+ end
17
+
18
+ it "should turn raise a custom exception when an attempt to access a protected user's tweets is made" do
19
+ protected_user_access_error = Twitter::RESTError.new
20
+ protected_user_access_error.code = "401"
21
+
22
+ client = mock
23
+ client.stub!(:gather_recent_tweets_for).with("protected_user").and_raise(protected_user_access_error)
24
+
25
+ lambda{TwitterClientWrapper.new(client).gather_recent_tweets_for("protected_user")}.should raise_error(ProtectedUserAccessError)
26
+ end
27
+
28
+ it "should turn any other exceptiona raised by client into a generci exception" do
29
+ access_error = Twitter::RESTError.new
30
+ access_error.code = "404"
31
+
32
+ client = mock
33
+ client.stub!(:gather_recent_tweets_for).with("dodgy_user").and_raise(access_error)
34
+
35
+ lambda{TwitterClientWrapper.new(client).gather_recent_tweets_for("dodgy_user")}.should raise_error(TwitterClientError)
36
+ end
37
+ end
@@ -0,0 +1,47 @@
1
+ require 'spec'
2
+ require 'twittertypeinferrer'
3
+ require 'twitterclient'
4
+
5
+ include TwitterType
6
+
7
+ describe TypeInferrer do
8
+
9
+ VALID_TWITTER_USER = "andee_marks"
10
+
11
+ Tweet = Struct.new(:text, :to_user)
12
+
13
+ before(:each) do
14
+ @cut = TypeInferrer.new
15
+ @mock_client = mock()
16
+ end
17
+
18
+ it "should fail gracefully if the Twitter client fails in some way" do
19
+ @mock_client.stub!(:gather_recent_tweets_for).with(VALID_TWITTER_USER).and_raise(TwitterClientError.new(nil))
20
+ @cut.client = @mock_client
21
+
22
+ @cut.profile.should == nil
23
+ lambda {@cut.infer(VALID_TWITTER_USER)}.should raise_error(TwitterClientError)
24
+ @cut.profile.should == nil
25
+ end
26
+
27
+ it "should use an unknown_unauthorised type for any protected screen name" do
28
+ @mock_client.stub!(:gather_recent_tweets_for).with(VALID_TWITTER_USER).and_raise(ProtectedUserAccessError.new(nil))
29
+ @cut.client = @mock_client
30
+
31
+ @cut.profile.should == nil
32
+ @cut.infer(VALID_TWITTER_USER)
33
+ @cut.profile.inferred_type.should == :unknown_protected_user
34
+
35
+ end
36
+
37
+ it "should infer a type for a Twitter User" do
38
+ tweets = Array.new(1) {|i| Tweet.new("test", "to user")}
39
+ @mock_client.stub!(:gather_recent_tweets_for).with(VALID_TWITTER_USER).and_return(tweets)
40
+
41
+ @cut.client = @mock_client
42
+
43
+ @cut.profile.should == nil
44
+ @cut.infer(VALID_TWITTER_USER)
45
+ @cut.profile.inferred_type.should_not == :undetermined
46
+ end
47
+ end
metadata ADDED
@@ -0,0 +1,86 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: twittertype
3
+ version: !ruby/object:Gem::Version
4
+ prerelease: false
5
+ segments:
6
+ - 0
7
+ - 1
8
+ - 0
9
+ version: 0.1.0
10
+ platform: ruby
11
+ authors:
12
+ - Andy Marks
13
+ autorequire:
14
+ bindir: bin
15
+ cert_chain: []
16
+
17
+ date: 2010-03-01 00:00:00 +11:00
18
+ default_executable:
19
+ dependencies:
20
+ - !ruby/object:Gem::Dependency
21
+ name: twitter4r
22
+ prerelease: false
23
+ requirement: &id001 !ruby/object:Gem::Requirement
24
+ requirements:
25
+ - - ">="
26
+ - !ruby/object:Gem::Version
27
+ segments:
28
+ - 0
29
+ - 3
30
+ - 2
31
+ version: 0.3.2
32
+ type: :runtime
33
+ version_requirements: *id001
34
+ description:
35
+ email: andy@corvine.org
36
+ executables: []
37
+
38
+ extensions: []
39
+
40
+ extra_rdoc_files:
41
+ - README.rdoc
42
+ files:
43
+ - lib/profilefactory.rb
44
+ - lib/tweeterprofile.rb
45
+ - lib/twitter_type.rb
46
+ - lib/twitterclient.rb
47
+ - lib/twitterclientwrapper.rb
48
+ - lib/twittertypeinferrer.rb
49
+ - README.rdoc
50
+ has_rdoc: true
51
+ homepage: http://github.com/andeemarks/Twitter-Type
52
+ licenses: []
53
+
54
+ post_install_message:
55
+ rdoc_options: []
56
+
57
+ require_paths:
58
+ - lib
59
+ required_ruby_version: !ruby/object:Gem::Requirement
60
+ requirements:
61
+ - - ">="
62
+ - !ruby/object:Gem::Version
63
+ segments:
64
+ - 0
65
+ version: "0"
66
+ required_rubygems_version: !ruby/object:Gem::Requirement
67
+ requirements:
68
+ - - ">="
69
+ - !ruby/object:Gem::Version
70
+ segments:
71
+ - 0
72
+ version: "0"
73
+ requirements: []
74
+
75
+ rubyforge_project:
76
+ rubygems_version: 1.3.6
77
+ signing_key:
78
+ specification_version: 3
79
+ summary: A utility for analysing Twitter user tweet patterns
80
+ test_files:
81
+ - test/functional/twittertypeinferrer_spec.rb
82
+ - test/unit/profilefactory_spec.rb
83
+ - test/unit/tweeterprofile_spec.rb
84
+ - test/unit/twitterclient_spec.rb
85
+ - test/unit/twitterclientwrapper_spec.rb
86
+ - test/unit/twittertypeinferrer_spec.rb