share_counts 0.0.9 → 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/.autotest +4 -0
- data/Gemfile.lock +12 -6
- data/autotest/discover.rb +2 -0
- data/autotest/rules.rb +12 -0
- data/lib/share_counts.rb +25 -40
- data/lib/share_counts/array.rb +5 -0
- data/lib/share_counts/common.rb +17 -15
- data/lib/share_counts/reddit.rb +5 -11
- data/share_counts.gemspec +7 -3
- data/test/digg.json +59 -0
- data/test/facebook.json +1 -0
- data/test/googlebuzz.json +1 -0
- data/test/linkedin.json +1 -0
- data/test/reddit-by-domain.json +1 -0
- data/test/reddit-url-info.json +1 -0
- data/test/reddit.json +1 -0
- data/test/stumbleupon.html +42 -0
- data/test/test_helper.rb +127 -0
- data/test/test_reddit_module.rb +83 -0
- data/test/test_share_counts.rb +183 -0
- data/test/twitter.json +1 -0
- metadata +92 -12
- data/spec/share_count_spec.rb +0 -3
- data/spec/test_helper.rb +0 -157
data/.autotest
ADDED
data/Gemfile.lock
CHANGED
@@ -1,7 +1,8 @@
|
|
1
1
|
PATH
|
2
2
|
remote: .
|
3
3
|
specs:
|
4
|
-
share_counts (0.0.
|
4
|
+
share_counts (0.0.9)
|
5
|
+
SystemTimer
|
5
6
|
json
|
6
7
|
nokogiri
|
7
8
|
redis
|
@@ -10,19 +11,24 @@ PATH
|
|
10
11
|
GEM
|
11
12
|
remote: http://rubygems.org/
|
12
13
|
specs:
|
13
|
-
|
14
|
+
SystemTimer (1.2.3)
|
15
|
+
activesupport (3.0.5)
|
16
|
+
addressable (2.2.4)
|
17
|
+
crack (0.1.8)
|
14
18
|
json (1.5.1)
|
15
19
|
mime-types (1.16)
|
16
|
-
minitest (2.0.2)
|
17
20
|
nokogiri (1.4.4)
|
18
|
-
redis (2.
|
21
|
+
redis (2.2.0)
|
19
22
|
rest-client (1.6.1)
|
20
23
|
mime-types (>= 1.16)
|
24
|
+
webmock (1.6.2)
|
25
|
+
addressable (>= 2.2.2)
|
26
|
+
crack (>= 0.1.7)
|
21
27
|
|
22
28
|
PLATFORMS
|
23
29
|
ruby
|
24
30
|
|
25
31
|
DEPENDENCIES
|
26
|
-
|
27
|
-
minitest
|
32
|
+
activesupport
|
28
33
|
share_counts!
|
34
|
+
webmock
|
data/autotest/rules.rb
ADDED
data/lib/share_counts.rb
CHANGED
@@ -1,5 +1,5 @@
|
|
1
|
-
%w( rest_client json nokogiri redis
|
2
|
-
%w( caching common reddit ).each{ |file| load File.expand_path( File.join( File.dirname( __FILE__ ), "share_counts", "#{file}.rb" ) ) } # TODO: replace load with require
|
1
|
+
%w( rubygems rest_client json nokogiri redis ).each{ |lib| require lib }
|
2
|
+
%w( array caching common reddit ).each{ |file| load File.expand_path( File.join( File.dirname( __FILE__ ), "share_counts", "#{file}.rb" ) ) } # TODO: replace load with require
|
3
3
|
|
4
4
|
module ShareCounts
|
5
5
|
|
@@ -11,72 +11,57 @@ module ShareCounts
|
|
11
11
|
end
|
12
12
|
|
13
13
|
def self.supported_networks
|
14
|
-
%w(reddit digg twitter facebook
|
14
|
+
%w(reddit digg twitter facebook linkedin stumbleupon googlebuzz)
|
15
15
|
end
|
16
|
-
|
17
|
-
def self.reddit url
|
18
|
-
try("reddit", url) {
|
16
|
+
|
17
|
+
def self.reddit url, raise_exceptions = false
|
18
|
+
try("reddit", url, raise_exceptions) {
|
19
19
|
extract_count from_json( "http://www.reddit.com/api/info.json", :url => url ),
|
20
20
|
:selector => "data/children/data/score"
|
21
21
|
}
|
22
22
|
end
|
23
|
-
|
24
|
-
def self.reddit_with_permalink url
|
25
|
-
ShareCounts::Reddit.info_for url
|
23
|
+
|
24
|
+
def self.reddit_with_permalink url, raise_exceptions = false
|
25
|
+
ShareCounts::Reddit.info_for url, raise_exceptions
|
26
26
|
end
|
27
|
-
|
28
|
-
def self.digg url
|
29
|
-
try("digg", url) {
|
27
|
+
|
28
|
+
def self.digg url, raise_exceptions = false
|
29
|
+
try("digg", url, raise_exceptions) {
|
30
30
|
extract_count from_json( "http://services.digg.com/2.0/story.getInfo", :links => url ),
|
31
31
|
:selector => "stories/diggs"
|
32
32
|
}
|
33
33
|
end
|
34
34
|
|
35
|
-
def self.twitter url
|
36
|
-
try("twitter", url) {
|
35
|
+
def self.twitter url, raise_exceptions = false
|
36
|
+
try("twitter", url, raise_exceptions) {
|
37
37
|
extract_count from_json( "http://urls.api.twitter.com/1/urls/count.json", :url => url),
|
38
38
|
:selector => "count"
|
39
39
|
}
|
40
40
|
end
|
41
41
|
|
42
|
-
def self.facebook url
|
43
|
-
try("facebook", url) {
|
44
|
-
extract_count from_json("http://api.facebook.com/restserver.php", :v => "1.0", :method => "links.getStats",
|
45
|
-
:urls => url, :callback => "fb_sharepro_render", :format => "json" ), :selector => "share_count"
|
46
|
-
}
|
47
|
-
end
|
48
|
-
|
49
|
-
def self.fblike url
|
50
|
-
try("fblike", url) {
|
42
|
+
def self.facebook url, raise_exceptions = false
|
43
|
+
try("facebook", url, raise_exceptions) {
|
51
44
|
extract_count from_json("http://api.facebook.com/restserver.php", :v => "1.0", :method => "links.getStats",
|
52
|
-
:urls => url, :callback => "fb_sharepro_render", :format => "json" ), :selector => "
|
53
|
-
}
|
54
|
-
end
|
55
|
-
|
56
|
-
def self.fball url
|
57
|
-
try("fball", url) {
|
58
|
-
json = from_json("http://api.facebook.com/restserver.php", :v => "1.0",
|
59
|
-
:method => "links.getStats", :urls => url, :callback => "fb_sharepro_render", :format => "json"
|
60
|
-
).first.select{ |k,v| ["share_count", "like_count"].include? k }
|
45
|
+
:urls => url, :callback => "fb_sharepro_render", :format => "json" ), :selector => "total_count"
|
61
46
|
}
|
62
47
|
end
|
63
48
|
|
64
|
-
def self.linkedin url
|
65
|
-
try("linkedin", url) {
|
49
|
+
def self.linkedin url, raise_exceptions = false
|
50
|
+
try("linkedin", url, raise_exceptions) {
|
66
51
|
extract_count from_json("http://www.linkedin.com/cws/share-count",
|
67
52
|
:url => url, :callback => "IN.Tags.Share.handleCount" ), :selector => "count"
|
68
53
|
}
|
69
54
|
end
|
70
55
|
|
71
|
-
def self.googlebuzz url
|
72
|
-
try("googlebuzz", url) {
|
56
|
+
def self.googlebuzz url, raise_exceptions = false
|
57
|
+
try("googlebuzz", url, raise_exceptions) {
|
73
58
|
from_json("http://www.google.com/buzz/api/buzzThis/buzzCounter",
|
74
59
|
:url => url, :callback => "google_buzz_set_count" )[url]
|
75
60
|
}
|
76
61
|
end
|
77
62
|
|
78
|
-
def self.stumbleupon url
|
79
|
-
try("stumbleupon", url) {
|
63
|
+
def self.stumbleupon url, raise_exceptions = false
|
64
|
+
try("stumbleupon", url, raise_exceptions) {
|
80
65
|
Nokogiri::HTML.parse(
|
81
66
|
make_request("http://www.stumbleupon.com/badge/embed/5/", :url => url )
|
82
67
|
).xpath( "//body/div/ul/li[2]/a/span").text.to_i
|
@@ -86,11 +71,11 @@ module ShareCounts
|
|
86
71
|
def self.all url
|
87
72
|
supported_networks.inject({}) { |r, c| r[c.to_sym] = ShareCounts.send(c, url); r }
|
88
73
|
end
|
89
|
-
|
74
|
+
|
90
75
|
def self.selected url, selections
|
91
76
|
selections.map{|name| name.downcase}.select{|name| supported_networks.include? name.to_s}.inject({}) {
|
92
77
|
|r, c| r[c.to_sym] = ShareCounts.send(c, url); r }
|
93
78
|
end
|
94
|
-
|
79
|
+
|
95
80
|
end
|
96
81
|
|
data/lib/share_counts/common.rb
CHANGED
@@ -17,7 +17,7 @@ module ShareCounts
|
|
17
17
|
# NOTE: caching will be skipped if the block fails.
|
18
18
|
#
|
19
19
|
#
|
20
|
-
def try service, url, &block
|
20
|
+
def try service, url, raise_exceptions = false, &block
|
21
21
|
cache_key = "ShareCounts||#{service}||#{url}"
|
22
22
|
if cache_enabled?
|
23
23
|
if result = from_redis(cache_key)
|
@@ -33,6 +33,7 @@ module ShareCounts
|
|
33
33
|
end
|
34
34
|
rescue Exception => e
|
35
35
|
puts "Something went wrong with #{service}: #{e}"
|
36
|
+
raise e if raise_exceptions
|
36
37
|
end
|
37
38
|
|
38
39
|
|
@@ -49,25 +50,26 @@ module ShareCounts
|
|
49
50
|
def make_request *args
|
50
51
|
result = nil
|
51
52
|
attempts = 1
|
53
|
+
url = args.shift
|
54
|
+
params = args.inject({}) { |r, c| r.merge! c }
|
52
55
|
|
53
56
|
begin
|
54
|
-
timeout
|
55
|
-
url = args.shift
|
56
|
-
params = args.inject({}) { |r, c| r.merge! c }
|
57
|
-
response = RestClient.get url, { :params => params }
|
57
|
+
response = RestClient.get url, { :params => params, :timeout => 5 }
|
58
58
|
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
: response
|
65
|
-
end
|
59
|
+
# if a callback is specified, the expected response is in the format "callback_name(JSON data)";
|
60
|
+
# with the response ending with ";" and, in some cases, "\n"
|
61
|
+
result = params.keys.include?(:callback) \
|
62
|
+
? response.gsub(/^(.*);+\n*$/, "\\1").gsub(/^#{params[:callback]}\((.*)\)$/, "\\1") \
|
63
|
+
: response
|
66
64
|
|
67
65
|
rescue Exception => e
|
68
|
-
puts "Failed #{attempts} attempt(s)"
|
66
|
+
puts "Failed #{attempts} attempt(s) - #{e}"
|
69
67
|
attempts += 1
|
70
|
-
|
68
|
+
if attempts <= 3
|
69
|
+
retry
|
70
|
+
else
|
71
|
+
raise Exception
|
72
|
+
end
|
71
73
|
end
|
72
74
|
|
73
75
|
result
|
@@ -82,7 +84,7 @@ module ShareCounts
|
|
82
84
|
#
|
83
85
|
#
|
84
86
|
def from_json *args
|
85
|
-
JSON.parse
|
87
|
+
JSON.parse(make_request(*args))
|
86
88
|
end
|
87
89
|
|
88
90
|
#
|
data/lib/share_counts/reddit.rb
CHANGED
@@ -3,21 +3,15 @@ module ShareCounts
|
|
3
3
|
extend Common
|
4
4
|
extend Caching
|
5
5
|
|
6
|
-
def self.info_for url
|
7
|
-
try("reddit-details", url) {
|
6
|
+
def self.info_for url, raise_exceptions = false
|
7
|
+
try("reddit-details", url, raise_exceptions) {
|
8
8
|
data = extract_info from_json( "http://www.reddit.com/api/info.json", :url => url ), :selector => "data/children/data"
|
9
|
-
|
10
|
-
data.reject{ |key, value|
|
11
|
-
%w( media_embed levenshtein selftext_html selftext likes saved clicked media over_18
|
12
|
-
hidden thumbnail subreddit_id is_self created subreddit_id created_utc num_comments
|
13
|
-
domain subreddit id author downs name url title ups
|
14
|
-
).include? key
|
15
|
-
}
|
9
|
+
data.select{|k, v| ["permalink", "score"].include? k }.map{|x| { x[0] => x[1] } }.to_hash
|
16
10
|
}
|
17
11
|
end
|
18
12
|
|
19
|
-
def self.by_domain domain
|
20
|
-
try("reddit-domain", domain) {
|
13
|
+
def self.by_domain domain, raise_exceptions = false
|
14
|
+
try("reddit-domain", domain, raise_exceptions) {
|
21
15
|
urls = extract_info from_json("http://www.reddit.com/domain/#{domain}.json"), :selector => "data/children", :preserve_arrays => true
|
22
16
|
urls.inject({}) do |result, url_all_info|
|
23
17
|
url_data = extract_info(url_all_info, :selector => "data").reject{ |key, value| !["permalink", "score", "url"].include? key }
|
data/share_counts.gemspec
CHANGED
@@ -4,7 +4,7 @@ $:.push File.expand_path("../lib/share_counts", __FILE__)
|
|
4
4
|
|
5
5
|
Gem::Specification.new do |s|
|
6
6
|
s.name = "share_counts"
|
7
|
-
s.version = "0.0
|
7
|
+
s.version = "0.1.0"
|
8
8
|
s.platform = Gem::Platform::RUBY
|
9
9
|
s.authors = ["Vito Botta"]
|
10
10
|
s.email = ["vito@botta.name"]
|
@@ -16,9 +16,13 @@ Gem::Specification.new do |s|
|
|
16
16
|
s.add_dependency "json"
|
17
17
|
s.add_dependency "nokogiri"
|
18
18
|
s.add_dependency "redis"
|
19
|
+
s.add_dependency "SystemTimer"
|
19
20
|
|
20
|
-
s.add_development_dependency "
|
21
|
-
s.add_development_dependency "
|
21
|
+
s.add_development_dependency "webmock"
|
22
|
+
s.add_development_dependency "activesupport"
|
23
|
+
s.add_development_dependency "autotest-growl"
|
24
|
+
s.add_development_dependency "autotest-fsevent"
|
25
|
+
s.add_development_dependency "redgreen"
|
22
26
|
|
23
27
|
s.rubyforge_project = "share_counts"
|
24
28
|
|
data/test/digg.json
ADDED
@@ -0,0 +1,59 @@
|
|
1
|
+
{
|
2
|
+
"count": 1,
|
3
|
+
"timestamp": 1301842122,
|
4
|
+
"cursor": "",
|
5
|
+
"version": "2.0",
|
6
|
+
"stories": [
|
7
|
+
{
|
8
|
+
"status": "upcoming",
|
9
|
+
"permalink": "http://digg.com/news/technology/geeky_cv_resume_d",
|
10
|
+
"description": "It's a not a new idea, I've seen something similar some time ago -I think it was in Perl but am not sure, but I remembered it today while updating my CV, so I thought I'd have a short, geekier version too :D",
|
11
|
+
"title": "Geeky CV / Resume :D",
|
12
|
+
"url": "http://vitobotta.com/cv-resume/",
|
13
|
+
"story_id": "20110205002738:141560b8-f74a-4388-9c95-d8638ca4a003",
|
14
|
+
"diggs": 1,
|
15
|
+
"submiter": {
|
16
|
+
"username": "vitobotta",
|
17
|
+
"about": "I am a passionate developer with 10+ years commercial experience and 360\u00b0 skills in the design, development and maintenance of modern, user centred as well as enterprise-scale web applications on both *nix and Microsoft platforms, with a strong interest in performance, scalability, security and search engine optimisation. \n\nBesides work, I live in London with my wife and our little baby daughter. I love boxing, martial arts, and good food!",
|
18
|
+
"user_id": "4341699",
|
19
|
+
"name": "Vito Botta",
|
20
|
+
"icons": [
|
21
|
+
"http://cdn1.diggstatic.com/user/4341699/c.4057865463.png",
|
22
|
+
"http://cdn1.diggstatic.com/user/4341699/h.4057865463.png",
|
23
|
+
"http://cdn2.diggstatic.com/user/4341699/m.4057865463.png",
|
24
|
+
"http://cdn1.diggstatic.com/user/4341699/l.4057865463.png",
|
25
|
+
"http://cdn2.diggstatic.com/user/4341699/p.4057865463.png",
|
26
|
+
"http://cdn1.diggstatic.com/user/4341699/s.4057865463.png",
|
27
|
+
"http://cdn1.diggstatic.com/user/4341699/r.4057865463.png"
|
28
|
+
],
|
29
|
+
"gender": "m",
|
30
|
+
"diggs": 34,
|
31
|
+
"comments": 31,
|
32
|
+
"followers": 11,
|
33
|
+
"location": "London",
|
34
|
+
"following": 8,
|
35
|
+
"submissions": 32,
|
36
|
+
"icon": "http://cdn1.diggstatic.com/user/4341699/p.4057865463.png"
|
37
|
+
},
|
38
|
+
"comments": 0,
|
39
|
+
"dugg": 0,
|
40
|
+
"topic": {
|
41
|
+
"clean_name": "technology",
|
42
|
+
"name": "Technology"
|
43
|
+
},
|
44
|
+
"promote_date": null,
|
45
|
+
"activity": [],
|
46
|
+
"date_created": 1296865658,
|
47
|
+
"thumbnails": {
|
48
|
+
"large": "http://cdn1.diggstatic.com/story/geeky_cv_resume_d/l.png",
|
49
|
+
"small": "http://cdn2.diggstatic.com/story/geeky_cv_resume_d/s.png",
|
50
|
+
"medium": "http://cdn1.diggstatic.com/story/geeky_cv_resume_d/m.png",
|
51
|
+
"thumb": "http://cdn1.diggstatic.com/story/geeky_cv_resume_d/t.png"
|
52
|
+
}
|
53
|
+
}
|
54
|
+
],
|
55
|
+
"authorized": 0,
|
56
|
+
"data": "stories",
|
57
|
+
"method": "story.getInfo",
|
58
|
+
"user": null
|
59
|
+
}
|
data/test/facebook.json
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
fb_sharepro_render([{"url":"http:\/\/vitobotta.com\/cv-resume\/","normalized_url":"http:\/\/www.vitobotta.com\/cv-resume\/","share_count":5,"like_count":17,"comment_count":1,"total_count":23,"click_count":0,"comments_fbid":10150098759753676,"commentsbox_count":0}]);
|
@@ -0,0 +1 @@
|
|
1
|
+
google_buzz_set_count({"http://vitobotta.com/cv-resume/":1});
|
data/test/linkedin.json
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
IN.Tags.Share.handleCount({"count":23,"url":"http://vitobotta.com/cv-resume/"});
|
@@ -0,0 +1 @@
|
|
1
|
+
{"kind": "Listing", "data": {"modhash": "", "children": [{"kind": "t3", "data": {"domain": "vitobotta.com", "media_embed": {}, "levenshtein": null, "subreddit": "Wordpress", "selftext_html": null, "selftext": "", "likes": null, "saved": false, "id": "g51dn", "clicked": false, "author": "VitoBotta", "media": null, "score": 8, "over_18": false, "hidden": false, "thumbnail": "", "subreddit_id": "t5_2qhjq", "downs": 3, "is_self": false, "permalink": "/r/Wordpress/comments/g51dn/protect_your_wordpress_blogs_administration_from/", "name": "t3_g51dn", "created": 1300268647.0, "url": "http://vitobotta.com/protect-wordpress-blogs-administration-prying-eyes/", "title": "Protect your Wordpress blog's administration from prying eyes", "created_utc": 1300268647.0, "num_comments": 5, "ups": 11}}, {"kind": "t3", "data": {"domain": "vitobotta.com", "media_embed": {}, "levenshtein": null, "subreddit": "geek", "selftext_html": null, "selftext": "", "likes": null, "saved": false, "id": "fuepg", "clicked": false, "author": "VitoBotta", "media": null, "score": 4, "over_18": false, "hidden": false, "thumbnail": "", "subreddit_id": "t5_2qh17", "downs": 3, "is_self": false, "permalink": "/r/geek/comments/fuepg/why_isnt_ssl_turned_on_by_default_for_all_websites/", "name": "t3_fuepg", "created": 1298913557.0, "url": "http://vitobotta.com/why-isnt-ssl-on-by-default-for-all-websites/", "title": "Why isn't SSL turned on by default for all websites?", "created_utc": 1298913557.0, "num_comments": 7, "ups": 7}}, {"kind": "t3", "data": {"domain": "vitobotta.com", "media_embed": {}, "levenshtein": null, "subreddit": "apple", "selftext_html": null, "selftext": "", "likes": null, "saved": false, "id": "fmkxz", "clicked": false, "author": "VitoBotta", "media": null, "score": 22, "over_18": false, "hidden": false, "thumbnail": "", "subreddit_id": "t5_2qh1f", "downs": 6, "is_self": false, "permalink": "/r/apple/comments/fmkxz/faster_internet_browsing_with_alternative_dns/", "name": "t3_fmkxz", "created": 1297876909.0, "url": "http://vitobotta.com/faster-internet-browsing-alternative-dns-servers-fast-local-cache-bind/", "title": "Faster Internet browsing with alternative DNS servers and a local cache on Mac OS X", "created_utc": 1297876909.0, "num_comments": 11, "ups": 28}}, {"kind": "t3", "data": {"domain": "vitobotta.com", "media_embed": {}, "levenshtein": null, "subreddit": "web_design", "selftext_html": null, "selftext": "", "likes": null, "saved": false, "id": "fhw72", "clicked": false, "author": "VitoBotta", "media": null, "score": 3, "over_18": false, "hidden": false, "thumbnail": "", "subreddit_id": "t5_2qh1m", "downs": 3, "is_self": false, "permalink": "/r/web_design/comments/fhw72/a_uptodate_look_at_the_state_of_web_typography/", "name": "t3_fhw72", "created": 1297220858.0, "url": "http://vitobotta.com/web-typography-techniques-usability-performance-seo-security/", "title": "A up-to-date look at the state of web typography with considerations for usability, performance, SEO and security", "created_utc": 1297220858.0, "num_comments": 0, "ups": 6}}, {"kind": "t3", "data": {"domain": "vitobotta.com", "media_embed": {}, "levenshtein": null, "subreddit": "ruby", "selftext_html": null, "selftext": "", "likes": null, "saved": false, "id": "ffik5", "clicked": false, "author": "VitoBotta", "media": null, "score": 29, "over_18": false, "hidden": false, "thumbnail": "", "subreddit_id": "t5_2qh21", "downs": 9, "is_self": false, "permalink": "/r/ruby/comments/ffik5/geeky_cv_d/", "name": "t3_ffik5", "created": 1296864258.0, "url": "http://vitobotta.com/cv-resume/", "title": "Geeky CV :D", "created_utc": 1296864258.0, "num_comments": 24, "ups": 38}}, {"kind": "t3", "data": {"domain": "vitobotta.com", "media_embed": {}, "levenshtein": null, "subreddit": "ruby", "selftext_html": null, "selftext": "", "likes": null, "saved": false, "id": "fceyq", "clicked": false, "author": "VitoBotta", "media": null, "score": 4, "over_18": false, "hidden": false, "thumbnail": "", "subreddit_id": "t5_2qh21", "downs": 1, "is_self": false, "permalink": "/r/ruby/comments/fceyq/share_counts_ruby_gem_the_easiest_way_to_check/", "name": "t3_fceyq", "created": 1296487510.0, "url": "http://vitobotta.com/share-counts-gem-social-networks/", "title": "share_counts Ruby gem: The easiest way to check how many times a URL has been shared on social networks!", "created_utc": 1296487510.0, "num_comments": 0, "ups": 5}}, {"kind": "t3", "data": {"domain": "vitobotta.com", "media_embed": {}, "levenshtein": null, "subreddit": "ruby", "selftext_html": null, "selftext": "", "likes": null, "saved": false, "id": "fb585", "clicked": false, "author": "VitoBotta", "media": null, "score": 2, "over_18": false, "hidden": false, "thumbnail": "", "subreddit_id": "t5_2qh21", "downs": 2, "is_self": false, "permalink": "/r/ruby/comments/fb585/a_serialisable_and_validatable_tableless_model_to/", "name": "t3_fb585", "created": 1296271706.0, "url": "http://vitobotta.com/serialisable-validatable-tableless-model/", "title": "A serialisable and validatable tableless model to get rid of a few tables and speed things up", "created_utc": 1296271706.0, "num_comments": 1, "ups": 4}}, {"kind": "t3", "data": {"domain": "vitobotta.com", "media_embed": {}, "levenshtein": null, "subreddit": "Database", "selftext_html": null, "selftext": "", "likes": null, "saved": false, "id": "faaji", "clicked": false, "author": "VitoBotta", "media": null, "score": 5, "over_18": false, "hidden": false, "thumbnail": "http://thumbs.reddit.com/t3_faaji.png", "subreddit_id": "t5_2qian", "downs": 2, "is_self": false, "permalink": "/r/Database/comments/faaji/smarter_faster_backups_and_restores_of_mysql/", "name": "t3_faaji", "created": 1296163917.0, "url": "http://vitobotta.com/smarter-faster-backups-restores-mysql-databases-with-mysqldump/", "title": "Smarter, faster backups and restores of MySQL databases using mysqldump - and other useful tips", "created_utc": 1296163917.0, "num_comments": 4, "ups": 7}}, {"kind": "t3", "data": {"domain": "vitobotta.com", "media_embed": {}, "levenshtein": null, "subreddit": "ruby", "selftext_html": null, "selftext": "", "likes": null, "saved": false, "id": "fa2k4", "clicked": false, "author": "trustfundbaby", "media": null, "score": 7, "over_18": false, "hidden": false, "thumbnail": "", "subreddit_id": "t5_2qh21", "downs": 1, "is_self": false, "permalink": "/r/ruby/comments/fa2k4/why_you_should_think_twice_before_using_awesome/", "name": "t3_fa2k4", "created": 1296142177.0, "url": "http://vitobotta.com/awesomeprint-similar-production/", "title": "Why you should think twice before using awesome_print (and similar gems) in production", "created_utc": 1296142177.0, "num_comments": 0, "ups": 8}}, {"kind": "t3", "data": {"domain": "vitobotta.com", "media_embed": {}, "levenshtein": null, "subreddit": "webdev", "selftext_html": null, "selftext": "", "likes": null, "saved": false, "id": "g8yy9", "clicked": false, "author": "VitoBotta", "media": null, "score": 0, "over_18": false, "hidden": false, "thumbnail": "http://thumbs.reddit.com/t3_g8yy9.png", "subreddit_id": "t5_2qs0q", "downs": 2, "is_self": false, "permalink": "/r/webdev/comments/g8yy9/migrating_from_wordpress_to_jekyll_part_1_why_i/", "name": "t3_g8yy9", "created": 1300806073.0, "url": "http://vitobotta.com/migrating-from-wordpress-to-jekyll-part-one-why-I-gave-up-on-wordpress/", "title": "Migrating from Wordpress to Jekyll - Part 1: Why I gave up on Wordpress", "created_utc": 1300806073.0, "num_comments": 3, "ups": 2}}, {"kind": "t3", "data": {"domain": "vitobotta.com", "media_embed": {}, "levenshtein": null, "subreddit": "Database", "selftext_html": null, "selftext": "", "likes": null, "saved": false, "id": "fc3q5", "clicked": false, "author": "VitoBotta", "media": null, "score": 0, "over_18": false, "hidden": false, "thumbnail": "http://thumbs.reddit.com/t3_fc3q5.png", "subreddit_id": "t5_2qian", "downs": 2, "is_self": false, "permalink": "/r/Database/comments/fc3q5/painless_ultra_fast_hot_backups_and_restores_of/", "name": "t3_fc3q5", "created": 1296438817.0, "url": "http://vitobotta.com/painless-hot-backups-mysql-live-databases-percona-xtrabackup/", "title": "Painless, ultra fast hot backups and restores of MySQL databases with Percona\u2019s XtraBackup", "created_utc": 1296438817.0, "num_comments": 2, "ups": 2}}], "after": null, "before": null}}
|
@@ -0,0 +1 @@
|
|
1
|
+
{"kind": "Listing", "data": {"modhash": "", "children": [{"kind": "t3", "data": {"domain": "vitobotta.com", "media_embed": {}, "levenshtein": null, "subreddit": "ruby", "selftext_html": null, "selftext": "", "likes": null, "saved": false, "id": "ffik5", "clicked": false, "author": "VitoBotta", "media": null, "score": 30, "over_18": false, "hidden": false, "thumbnail": "", "subreddit_id": "t5_2qh21", "downs": 9, "is_self": false, "permalink": "/r/ruby/comments/ffik5/geeky_cv_d/", "name": "t3_ffik5", "created": 1296864258.0, "url": "http://vitobotta.com/cv-resume/", "title": "Geeky CV :D", "created_utc": 1296864258.0, "num_comments": 24, "ups": 39}}], "after": null, "before": null}}
|
data/test/reddit.json
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
{"kind": "Listing", "data": {"modhash": "", "children": [{"kind": "t3", "data": {"domain": "vitobotta.com", "media_embed": {}, "levenshtein": null, "subreddit": "ruby", "selftext_html": null, "selftext": "", "likes": null, "saved": false, "id": "ffik5", "clicked": false, "author": "VitoBotta", "media": null, "score": 31, "over_18": false, "hidden": false, "thumbnail": "", "subreddit_id": "t5_2qh21", "downs": 10, "is_self": false, "permalink": "/r/ruby/comments/ffik5/geeky_cv_d/", "name": "t3_ffik5", "created": 1296864258.0, "url": "http://vitobotta.com/cv-resume/", "title": "Geeky CV :D", "created_utc": 1296864258.0, "num_comments": 24, "ups": 41}}], "after": null, "before": null}}
|
@@ -0,0 +1,42 @@
|
|
1
|
+
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
|
2
|
+
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
|
3
|
+
|
4
|
+
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en" xmlns:fb="http://www.facebook.com/2008/fbml">
|
5
|
+
<head>
|
6
|
+
<meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
|
7
|
+
|
8
|
+
|
9
|
+
<link rel="stylesheet" href="http://cdn.stumble-upon.com/css/badges_su.css?v=20110331a" type="text/css" media="screen, projection" />
|
10
|
+
|
11
|
+
<script type="text/javascript" src="http://cdn.stumble-upon.com/js/badge_su.js?v=20110331a"></script>
|
12
|
+
|
13
|
+
|
14
|
+
<title></title>
|
15
|
+
|
16
|
+
</head>
|
17
|
+
<body class="badge5">
|
18
|
+
|
19
|
+
<div id="wrapper">
|
20
|
+
|
21
|
+
|
22
|
+
|
23
|
+
|
24
|
+
|
25
|
+
|
26
|
+
|
27
|
+
|
28
|
+
<ul class="suHostedBadge">
|
29
|
+
<li><a class="logo" onClick="javascript: SUJS.buttonWin.open('http://www.stumbleupon.com/badge/?url=http://vitobotta.com/cv-resume/'); void(0);" href="javascript:void(0);" target="_top">StumbleUpon</a></li>
|
30
|
+
<li>
|
31
|
+
<a class="count" onClick="javascript: SUJS.buttonWin.open('http://www.stumbleupon.com/badge/?url=http://vitobotta.com/cv-resume/'); void(0);" href="javascript:void(0);" target="_top">
|
32
|
+
<span>6</span>
|
33
|
+
</a>
|
34
|
+
</li>
|
35
|
+
</ul>
|
36
|
+
</div> <!-- end wrapper -->
|
37
|
+
|
38
|
+
|
39
|
+
<span id="__su_server_time__" class="1301846270"></span>
|
40
|
+
|
41
|
+
</body>
|
42
|
+
</html>
|
data/test/test_helper.rb
ADDED
@@ -0,0 +1,127 @@
|
|
1
|
+
%w(rubygems active_support webmock/test_unit stringio system_timer).each{|g| require g}
|
2
|
+
|
3
|
+
include WebMock::API
|
4
|
+
|
5
|
+
require File.join(File.dirname(__FILE__), "../lib/share_counts")
|
6
|
+
|
7
|
+
SOME_URL = "http://vitobotta.com/cv-resume/"
|
8
|
+
SOME_PARAMS = [ :url => "http://vitobotta.com/cv-resume/", :callback => "myCallback" ]
|
9
|
+
|
10
|
+
class ActiveSupport::TestCase
|
11
|
+
setup do
|
12
|
+
$stderr = @stderr = StringIO.new
|
13
|
+
$stdin = @stdin = StringIO.new
|
14
|
+
$stdout = @stdout = StringIO.new
|
15
|
+
end
|
16
|
+
|
17
|
+
def teardown
|
18
|
+
$stderr = @stderr = STDERR
|
19
|
+
$stdin = @stdin = STDIN
|
20
|
+
$stdout = @stdout = STDOUT
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
class Reddit
|
25
|
+
def self.params
|
26
|
+
[:url => SOME_URL]
|
27
|
+
end
|
28
|
+
def self.api
|
29
|
+
"http://www.reddit.com/api/info.json"
|
30
|
+
end
|
31
|
+
def self.json
|
32
|
+
@json ||= File.read(File.join(File.dirname(__FILE__), "reddit.json"))
|
33
|
+
end
|
34
|
+
def self.url_info_json
|
35
|
+
@url_info_json ||= File.read(File.join(File.dirname(__FILE__), "reddit-url-info.json"))
|
36
|
+
end
|
37
|
+
def self.by_domain_json
|
38
|
+
@by_domain_json ||= File.read(File.join(File.dirname(__FILE__), "reddit-by-domain.json"))
|
39
|
+
end
|
40
|
+
def self.selector
|
41
|
+
"data/children/data/score"
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
class Digg
|
46
|
+
def self.api
|
47
|
+
"http://services.digg.com/2.0/story.getInfo"
|
48
|
+
end
|
49
|
+
def self.params
|
50
|
+
[:links => SOME_URL]
|
51
|
+
end
|
52
|
+
def self.json
|
53
|
+
@json ||= File.read(File.join(File.dirname(__FILE__), "digg.json"))
|
54
|
+
end
|
55
|
+
def self.selector
|
56
|
+
"stories/diggs"
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
class Twitter
|
61
|
+
def self.api
|
62
|
+
"http://urls.api.twitter.com/1/urls/count.json"
|
63
|
+
end
|
64
|
+
def self.params
|
65
|
+
[:url => SOME_URL]
|
66
|
+
end
|
67
|
+
def self.json
|
68
|
+
@json ||= File.read(File.join(File.dirname(__FILE__), "twitter.json"))
|
69
|
+
end
|
70
|
+
def self.selector
|
71
|
+
"count"
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
class Facebook
|
76
|
+
def self.api
|
77
|
+
"http://api.facebook.com/restserver.php"
|
78
|
+
end
|
79
|
+
def self.json
|
80
|
+
@json ||= File.read(File.join(File.dirname(__FILE__), "facebook.json"))
|
81
|
+
end
|
82
|
+
def self.params
|
83
|
+
[:v => "1.0", :method => "links.getStats", :urls => SOME_URL, :callback => "fb_sharepro_render", :format => "json"]
|
84
|
+
end
|
85
|
+
def self.selector
|
86
|
+
"total_count"
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
class Linkedin
|
91
|
+
def self.api
|
92
|
+
"http://www.linkedin.com/cws/share-count"
|
93
|
+
end
|
94
|
+
def self.params
|
95
|
+
[:url => SOME_URL, :callback => "IN.Tags.Share.handleCount"]
|
96
|
+
end
|
97
|
+
def self.json
|
98
|
+
@json ||= File.read(File.join(File.dirname(__FILE__), "linkedin.json"))
|
99
|
+
end
|
100
|
+
def self.selector
|
101
|
+
"count"
|
102
|
+
end
|
103
|
+
end
|
104
|
+
|
105
|
+
class GoogleBuzz
|
106
|
+
def self.api
|
107
|
+
"http://www.google.com/buzz/api/buzzThis/buzzCounter"
|
108
|
+
end
|
109
|
+
def self.params
|
110
|
+
[:url => SOME_URL, :callback => "google_buzz_set_count"]
|
111
|
+
end
|
112
|
+
def self.json
|
113
|
+
@json ||= File.read(File.join(File.dirname(__FILE__), "googlebuzz.json"))
|
114
|
+
end
|
115
|
+
end
|
116
|
+
|
117
|
+
class StumbleUpon
|
118
|
+
def self.api
|
119
|
+
"http://www.stumbleupon.com/badge/embed/5/"
|
120
|
+
end
|
121
|
+
def self.params
|
122
|
+
[:url => SOME_URL]
|
123
|
+
end
|
124
|
+
def self.html
|
125
|
+
@json ||= File.read(File.join(File.dirname(__FILE__), "stumbleupon.html"))
|
126
|
+
end
|
127
|
+
end
|
@@ -0,0 +1,83 @@
|
|
1
|
+
require File.expand_path(File.join(File.dirname(__FILE__), "test_helper"))
|
2
|
+
|
3
|
+
class RedditModuleTest < ActiveSupport::TestCase
|
4
|
+
test ".info_for should return a hash with score and permalink for the given url" do
|
5
|
+
stub_request(:get, Reddit.api).with(:query => Reddit.params.to_hash).to_return(:body => Reddit.url_info_json)
|
6
|
+
assert_equal({ "permalink" => "/r/ruby/comments/ffik5/geeky_cv_d/", "score" => 30}, ShareCounts::Reddit.info_for(SOME_URL))
|
7
|
+
end
|
8
|
+
|
9
|
+
test ".info_for with raise_exceptions=true should raise exception" do
|
10
|
+
stub_request(:get, Reddit.api).with(:query => Reddit.params.to_hash).to_raise(Exception)
|
11
|
+
assert_raise(Exception) { ShareCounts::Reddit.info_for(SOME_URL, true) }
|
12
|
+
end
|
13
|
+
|
14
|
+
test ".by_domain should return permalink and score for each URL Reddit knows for the given domain" do
|
15
|
+
stub_request(:get, "http://www.reddit.com/domain/vitobotta.com.json").to_return(:body => Reddit.by_domain_json)
|
16
|
+
|
17
|
+
result = {
|
18
|
+
"http://vitobotta.com/protect-wordpress-blogs-administration-prying-eyes/" => {
|
19
|
+
"permalink" => "/r/Wordpress/comments/g51dn/protect_your_wordpress_blogs_administration_from/",
|
20
|
+
"score" => 8
|
21
|
+
},
|
22
|
+
|
23
|
+
"http://vitobotta.com/why-isnt-ssl-on-by-default-for-all-websites/" => {
|
24
|
+
"permalink" => "/r/geek/comments/fuepg/why_isnt_ssl_turned_on_by_default_for_all_websites/",
|
25
|
+
"score" => 4
|
26
|
+
},
|
27
|
+
|
28
|
+
"http://vitobotta.com/faster-internet-browsing-alternative-dns-servers-fast-local-cache-bind/" => {
|
29
|
+
"permalink" => "/r/apple/comments/fmkxz/faster_internet_browsing_with_alternative_dns/",
|
30
|
+
"score" => 22
|
31
|
+
},
|
32
|
+
|
33
|
+
"http://vitobotta.com/web-typography-techniques-usability-performance-seo-security/" => {
|
34
|
+
"permalink" => "/r/web_design/comments/fhw72/a_uptodate_look_at_the_state_of_web_typography/",
|
35
|
+
"score" => 3
|
36
|
+
},
|
37
|
+
|
38
|
+
"http://vitobotta.com/cv-resume/" => {
|
39
|
+
"permalink" => "/r/ruby/comments/ffik5/geeky_cv_d/",
|
40
|
+
"score" => 29
|
41
|
+
},
|
42
|
+
|
43
|
+
"http://vitobotta.com/share-counts-gem-social-networks/" => {
|
44
|
+
"permalink" => "/r/ruby/comments/fceyq/share_counts_ruby_gem_the_easiest_way_to_check/",
|
45
|
+
"score" => 4
|
46
|
+
},
|
47
|
+
|
48
|
+
"http://vitobotta.com/serialisable-validatable-tableless-model/" => {
|
49
|
+
"permalink" => "/r/ruby/comments/fb585/a_serialisable_and_validatable_tableless_model_to/",
|
50
|
+
"score" => 2
|
51
|
+
},
|
52
|
+
|
53
|
+
"http://vitobotta.com/smarter-faster-backups-restores-mysql-databases-with-mysqldump/" => {
|
54
|
+
"permalink" => "/r/Database/comments/faaji/smarter_faster_backups_and_restores_of_mysql/",
|
55
|
+
"score" => 5
|
56
|
+
},
|
57
|
+
|
58
|
+
"http://vitobotta.com/awesomeprint-similar-production/" => {
|
59
|
+
"permalink" => "/r/ruby/comments/fa2k4/why_you_should_think_twice_before_using_awesome/",
|
60
|
+
"score" => 7
|
61
|
+
},
|
62
|
+
|
63
|
+
"http://vitobotta.com/migrating-from-wordpress-to-jekyll-part-one-why-I-gave-up-on-wordpress/" => {
|
64
|
+
"permalink" => "/r/webdev/comments/g8yy9/migrating_from_wordpress_to_jekyll_part_1_why_i/",
|
65
|
+
"score" => 0
|
66
|
+
},
|
67
|
+
|
68
|
+
"http://vitobotta.com/painless-hot-backups-mysql-live-databases-percona-xtrabackup/" => {
|
69
|
+
"permalink" => "/r/Database/comments/fc3q5/painless_ultra_fast_hot_backups_and_restores_of/",
|
70
|
+
"score" => 0
|
71
|
+
}
|
72
|
+
|
73
|
+
}
|
74
|
+
|
75
|
+
assert_equal(result, ShareCounts::Reddit.by_domain("vitobotta.com"))
|
76
|
+
end
|
77
|
+
|
78
|
+
|
79
|
+
test ".by_domain with raise_exceptions=true should raise exception" do
|
80
|
+
stub_request(:get, "http://www.reddit.com/domain/vitobotta.com.json").to_raise(Exception)
|
81
|
+
assert_raise(Exception) { ShareCounts::Reddit.by_domain("vitobotta.com", true) }
|
82
|
+
end
|
83
|
+
end
|
@@ -0,0 +1,183 @@
|
|
1
|
+
require File.expand_path(File.join(File.dirname(__FILE__), "test_helper"))
|
2
|
+
|
3
|
+
class ShareCountsTest < ActiveSupport::TestCase
|
4
|
+
|
5
|
+
def stub_all
|
6
|
+
stub_request(:get, Reddit.api).with(:query => Reddit.params.to_hash).to_return(:body => Reddit.json)
|
7
|
+
stub_request(:get, Digg.api).with(:query => Digg.params.to_hash).to_return(:body => Digg.json)
|
8
|
+
stub_request(:get, Facebook.api).with(:query => Facebook.params.to_hash).to_return(:body => Facebook.json)
|
9
|
+
stub_request(:get, Twitter.api).with(:query => Twitter.params.to_hash).to_return(:body => Twitter.json)
|
10
|
+
stub_request(:get, Linkedin.api).with(:query => Linkedin.params.to_hash).to_return(:body => Linkedin.json)
|
11
|
+
stub_request(:get, GoogleBuzz.api).with(:query => GoogleBuzz.params.to_hash).to_return(:body => GoogleBuzz.json)
|
12
|
+
stub_request(:get, StumbleUpon.api).with(:query => StumbleUpon.params.to_hash).to_return(:body => StumbleUpon.html)
|
13
|
+
end
|
14
|
+
|
15
|
+
test ".supported_networks returns the supported networks" do
|
16
|
+
assert_equal(%w(reddit digg twitter facebook linkedin googlebuzz stumbleupon).sort, ShareCounts.supported_networks.sort)
|
17
|
+
end
|
18
|
+
|
19
|
+
test ".make_request makes a request to a remove service and returns the response" do
|
20
|
+
stub_request(:get, SOME_URL).with(:query => SOME_PARAMS.to_hash).to_return(:body => "---RESPONSE---")
|
21
|
+
|
22
|
+
assert_equal("---RESPONSE---", ShareCounts.send(:make_request, SOME_URL, *SOME_PARAMS ))
|
23
|
+
assert_equal(0, @stdout.string.split("\n").size)
|
24
|
+
end
|
25
|
+
|
26
|
+
test ".make_request should raise an exception if the remote service returns a 500 status code for three attempts" do
|
27
|
+
stub_request(:get, SOME_URL).to_return(:status => [500, "Internal Server Error"])
|
28
|
+
|
29
|
+
assert_raise(Exception) { ShareCounts.send(:make_request, SOME_URL) }
|
30
|
+
|
31
|
+
errors = []
|
32
|
+
3.times {|n| errors << "Failed #{n+1} attempt(s) - 500 Internal Server Error" }
|
33
|
+
assert_equal(errors.sort, @stdout.string.split("\n").sort)
|
34
|
+
end
|
35
|
+
|
36
|
+
|
37
|
+
test ".make_request should raise an exception if the remote service times out for three attempts" do
|
38
|
+
stub_request(:get, SOME_URL).to_timeout
|
39
|
+
|
40
|
+
assert_raise(Exception) { ShareCounts.send(:make_request, SOME_URL) }
|
41
|
+
|
42
|
+
errors = []
|
43
|
+
3.times {|n| errors << "Failed #{n+1} attempt(s) - Request Timeout" }
|
44
|
+
assert_equal(errors.sort, @stdout.string.split("\n").sort)
|
45
|
+
end
|
46
|
+
|
47
|
+
test ".make_request should return response if remote service fails < 3 attempts" do
|
48
|
+
stub_request(:get, SOME_URL).
|
49
|
+
to_return(:status => [500, "Internal Server Error"]).then.
|
50
|
+
to_timeout.then.
|
51
|
+
to_return(:body => "---RESPONSE---" )
|
52
|
+
|
53
|
+
assert_nothing_raised(Exception) { assert_equal("---RESPONSE---", ShareCounts.send(:make_request, SOME_URL)) }
|
54
|
+
|
55
|
+
assert_equal(["Failed 1 attempt(s) - 500 Internal Server Error", "Failed 2 attempt(s) - Request Timeout"].sort, @stdout.string.split("\n").sort)
|
56
|
+
end
|
57
|
+
|
58
|
+
test ".make_request should strip the callback call from the JSON response if a callback has been specified" do
|
59
|
+
stub_request(:get, SOME_URL).with(:query => SOME_PARAMS.to_hash).
|
60
|
+
to_return(:body => "myCallback(JSON_DATA);").then.
|
61
|
+
to_return(:body => "myCallback(JSON_DATA)")
|
62
|
+
|
63
|
+
assert_equal("JSON_DATA", ShareCounts.send(:make_request, SOME_URL, *SOME_PARAMS ))
|
64
|
+
assert_equal("JSON_DATA", ShareCounts.send(:make_request, SOME_URL, *SOME_PARAMS ))
|
65
|
+
assert_equal(0, @stdout.string.split("\n").size)
|
66
|
+
end
|
67
|
+
|
68
|
+
|
69
|
+
test ".from_json parses the JSON response returned by a remote service" do
|
70
|
+
stub_request(:get, SOME_URL).to_return(:body => "{\"a\":1,\"b\":2}").then.to_return(:body => "[\"a\", \"b\", 1, 2]")
|
71
|
+
stub_request(:get, SOME_URL).with(:query => SOME_PARAMS.to_hash).to_return(:body => "myCallback({\"a\":1,\"b\":2})")
|
72
|
+
|
73
|
+
assert_equal({ "a" => 1, "b" => 2 }, ShareCounts.send(:from_json, SOME_URL))
|
74
|
+
assert_equal(["a", "b", 1, 2], ShareCounts.send(:from_json, SOME_URL))
|
75
|
+
assert_equal({ "a" => 1, "b" => 2 }, ShareCounts.send(:from_json, SOME_URL, *SOME_PARAMS ))
|
76
|
+
assert_equal(0, @stdout.string.split("\n").size)
|
77
|
+
end
|
78
|
+
|
79
|
+
test ".extract_info correctly extract the information from the parsed JSON data received, in XPATH style" do
|
80
|
+
teardown
|
81
|
+
|
82
|
+
stub_all
|
83
|
+
|
84
|
+
assert_equal(31, ShareCounts.send(:extract_info, ShareCounts.send(:from_json, Reddit.api, *Reddit.params), { :selector => Reddit.selector } ))
|
85
|
+
assert_equal(1, ShareCounts.send(:extract_info, ShareCounts.send(:from_json, Digg.api, *Digg.params), { :selector => Digg.selector } ))
|
86
|
+
assert_equal(35, ShareCounts.send(:extract_info, ShareCounts.send(:from_json, Twitter.api, *Twitter.params), { :selector => Twitter.selector } ))
|
87
|
+
assert_equal(23, ShareCounts.send(:extract_info, ShareCounts.send(:from_json, Facebook.api, *Facebook.params), { :selector => Facebook.selector } ))
|
88
|
+
assert_equal(23, ShareCounts.send(:extract_info, ShareCounts.send(:from_json, Linkedin.api, *Linkedin.params), { :selector => Linkedin.selector } ))
|
89
|
+
end
|
90
|
+
|
91
|
+
test ".reddit should return the reddit score" do
|
92
|
+
stub_request(:get, Reddit.api).with(:query => Reddit.params.to_hash).to_return(:body => Reddit.json)
|
93
|
+
assert_equal(31, ShareCounts.reddit(SOME_URL))
|
94
|
+
end
|
95
|
+
|
96
|
+
test ".reddit with raise_exceptions=true should raise exception" do
|
97
|
+
stub_request(:get, Reddit.api).with(:query => Reddit.params.to_hash).to_raise(Exception)
|
98
|
+
assert_raise(Exception) { ShareCounts.reddit(SOME_URL, true) }
|
99
|
+
end
|
100
|
+
|
101
|
+
test ".digg should return the digg score" do
|
102
|
+
stub_request(:get, Digg.api).with(:query => Digg.params.to_hash).to_return(:body => Digg.json)
|
103
|
+
assert_equal(1, ShareCounts.digg(SOME_URL))
|
104
|
+
end
|
105
|
+
|
106
|
+
test ".digg with raise_exceptions=true should raise exception" do
|
107
|
+
stub_request(:get, Digg.api).with(:query => Digg.params.to_hash).to_raise(Exception)
|
108
|
+
assert_raise(Exception) { ShareCounts.digg(SOME_URL, true) }
|
109
|
+
end
|
110
|
+
|
111
|
+
test ".twitter should return the twitter score" do
|
112
|
+
stub_request(:get, Twitter.api).with(:query => Twitter.params.to_hash).to_return(:body => Twitter.json)
|
113
|
+
assert_equal(35, ShareCounts.twitter(SOME_URL))
|
114
|
+
end
|
115
|
+
|
116
|
+
test ".twitter with raise_exceptions=true should raise exception" do
|
117
|
+
stub_request(:get, Twitter.api).with(:query => Twitter.params.to_hash).to_raise(Exception)
|
118
|
+
assert_raise(Exception) { ShareCounts.twitter(SOME_URL, true) }
|
119
|
+
end
|
120
|
+
|
121
|
+
test ".facebook should return the facebook score" do
|
122
|
+
stub_request(:get, Facebook.api).with(:query => Facebook.params.to_hash).to_return(:body => Facebook.json)
|
123
|
+
assert_equal(23, ShareCounts.facebook(SOME_URL))
|
124
|
+
end
|
125
|
+
|
126
|
+
test ".facebook with raise_exceptions=true should raise exception" do
|
127
|
+
stub_request(:get, Facebook.api).with(:query => Facebook.params.to_hash).to_raise(Exception)
|
128
|
+
assert_raise(Exception) { ShareCounts.facebook(SOME_URL, true) }
|
129
|
+
end
|
130
|
+
|
131
|
+
test ".linkedin should return the linkedin score" do
|
132
|
+
stub_request(:get, Linkedin.api).with(:query => Linkedin.params.to_hash).to_return(:body => Linkedin.json)
|
133
|
+
assert_equal(23, ShareCounts.linkedin(SOME_URL))
|
134
|
+
end
|
135
|
+
|
136
|
+
test ".linkedin with raise_exceptions=true should raise exception" do
|
137
|
+
stub_request(:get, Linkedin.api).with(:query => Linkedin.params.to_hash).to_raise(Exception)
|
138
|
+
assert_raise(Exception) { ShareCounts.linkedin(SOME_URL, true) }
|
139
|
+
end
|
140
|
+
|
141
|
+
test ".googlebuzz should return the googlebuzz score" do
|
142
|
+
stub_request(:get, GoogleBuzz.api).with(:query => GoogleBuzz.params.to_hash).to_return(:body => GoogleBuzz.json)
|
143
|
+
assert_equal(1, ShareCounts.googlebuzz(SOME_URL))
|
144
|
+
end
|
145
|
+
|
146
|
+
test ".googlebuzz with raise_exceptions=true should raise exception" do
|
147
|
+
stub_request(:get, GoogleBuzz.api).with(:query => GoogleBuzz.params.to_hash).to_raise(Exception)
|
148
|
+
assert_raise(Exception) { ShareCounts.googlebuzz(SOME_URL, true) }
|
149
|
+
end
|
150
|
+
|
151
|
+
test ".stumbleupon should return the stumbleupon score" do
|
152
|
+
stub_request(:get, StumbleUpon.api).with(:query => StumbleUpon.params.to_hash).to_return(:body => StumbleUpon.html)
|
153
|
+
assert_equal(6, ShareCounts.stumbleupon(SOME_URL))
|
154
|
+
end
|
155
|
+
|
156
|
+
test ".stumbleupon with raise_exceptions=true should raise exception" do
|
157
|
+
stub_request(:get, StumbleUpon.api).with(:query => StumbleUpon.params.to_hash).to_raise(Exception)
|
158
|
+
assert_raise(Exception) { ShareCounts.stumbleupon(SOME_URL, true) }
|
159
|
+
end
|
160
|
+
|
161
|
+
test ".reddit_with_permalink should return a hash with Reddit score and permalink" do
|
162
|
+
stub_request(:get, Reddit.api).with(:query => Reddit.params.to_hash).to_return(:body => Reddit.json)
|
163
|
+
assert_equal({ "permalink" => "/r/ruby/comments/ffik5/geeky_cv_d/", "score" => 31 }, ShareCounts.reddit_with_permalink(SOME_URL))
|
164
|
+
end
|
165
|
+
|
166
|
+
test ".reddit_with_permalink with raise_exceptions=true should raise exception" do
|
167
|
+
stub_request(:get, Reddit.api).with(:query => Reddit.params.to_hash).to_raise(Exception)
|
168
|
+
assert_raise(Exception) { ShareCounts.reddit_with_permalink(SOME_URL, true) }
|
169
|
+
end
|
170
|
+
|
171
|
+
test ".all should returns scores for all the known networks" do
|
172
|
+
stub_all
|
173
|
+
assert_equal({ :digg => 1, :reddit => 31, :twitter => 35, :facebook => 23, :linkedin => 23, :googlebuzz => 1, :linkedin => 23, :stumbleupon => 6 }, ShareCounts.all(SOME_URL))
|
174
|
+
end
|
175
|
+
|
176
|
+
test ".selected should only return scores for the networks specified" do
|
177
|
+
stub_request(:get, Twitter.api).with(:query => Twitter.params.to_hash).to_return(:body => Twitter.json)
|
178
|
+
stub_request(:get, Linkedin.api).with(:query => Linkedin.params.to_hash).to_return(:body => Linkedin.json)
|
179
|
+
|
180
|
+
assert_equal({ :twitter => 35, :linkedin => 23 }, ShareCounts.selected(SOME_URL, ["twitter", "linkedin"]))
|
181
|
+
end
|
182
|
+
|
183
|
+
end
|
data/test/twitter.json
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
{"count":35,"url":"http://vitobotta.com/cv-resume/"}
|
metadata
CHANGED
@@ -1,13 +1,13 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: share_counts
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
hash:
|
4
|
+
hash: 27
|
5
5
|
prerelease:
|
6
6
|
segments:
|
7
7
|
- 0
|
8
|
+
- 1
|
8
9
|
- 0
|
9
|
-
|
10
|
-
version: 0.0.9
|
10
|
+
version: 0.1.0
|
11
11
|
platform: ruby
|
12
12
|
authors:
|
13
13
|
- Vito Botta
|
@@ -15,7 +15,7 @@ autorequire:
|
|
15
15
|
bindir: bin
|
16
16
|
cert_chain: []
|
17
17
|
|
18
|
-
date: 2011-03
|
18
|
+
date: 2011-04-03 00:00:00 +01:00
|
19
19
|
default_executable:
|
20
20
|
dependencies:
|
21
21
|
- !ruby/object:Gem::Dependency
|
@@ -75,7 +75,7 @@ dependencies:
|
|
75
75
|
type: :runtime
|
76
76
|
version_requirements: *id004
|
77
77
|
- !ruby/object:Gem::Dependency
|
78
|
-
name:
|
78
|
+
name: SystemTimer
|
79
79
|
prerelease: false
|
80
80
|
requirement: &id005 !ruby/object:Gem::Requirement
|
81
81
|
none: false
|
@@ -86,10 +86,10 @@ dependencies:
|
|
86
86
|
segments:
|
87
87
|
- 0
|
88
88
|
version: "0"
|
89
|
-
type: :
|
89
|
+
type: :runtime
|
90
90
|
version_requirements: *id005
|
91
91
|
- !ruby/object:Gem::Dependency
|
92
|
-
name:
|
92
|
+
name: webmock
|
93
93
|
prerelease: false
|
94
94
|
requirement: &id006 !ruby/object:Gem::Requirement
|
95
95
|
none: false
|
@@ -102,6 +102,62 @@ dependencies:
|
|
102
102
|
version: "0"
|
103
103
|
type: :development
|
104
104
|
version_requirements: *id006
|
105
|
+
- !ruby/object:Gem::Dependency
|
106
|
+
name: activesupport
|
107
|
+
prerelease: false
|
108
|
+
requirement: &id007 !ruby/object:Gem::Requirement
|
109
|
+
none: false
|
110
|
+
requirements:
|
111
|
+
- - ">="
|
112
|
+
- !ruby/object:Gem::Version
|
113
|
+
hash: 3
|
114
|
+
segments:
|
115
|
+
- 0
|
116
|
+
version: "0"
|
117
|
+
type: :development
|
118
|
+
version_requirements: *id007
|
119
|
+
- !ruby/object:Gem::Dependency
|
120
|
+
name: autotest-growl
|
121
|
+
prerelease: false
|
122
|
+
requirement: &id008 !ruby/object:Gem::Requirement
|
123
|
+
none: false
|
124
|
+
requirements:
|
125
|
+
- - ">="
|
126
|
+
- !ruby/object:Gem::Version
|
127
|
+
hash: 3
|
128
|
+
segments:
|
129
|
+
- 0
|
130
|
+
version: "0"
|
131
|
+
type: :development
|
132
|
+
version_requirements: *id008
|
133
|
+
- !ruby/object:Gem::Dependency
|
134
|
+
name: autotest-fsevent
|
135
|
+
prerelease: false
|
136
|
+
requirement: &id009 !ruby/object:Gem::Requirement
|
137
|
+
none: false
|
138
|
+
requirements:
|
139
|
+
- - ">="
|
140
|
+
- !ruby/object:Gem::Version
|
141
|
+
hash: 3
|
142
|
+
segments:
|
143
|
+
- 0
|
144
|
+
version: "0"
|
145
|
+
type: :development
|
146
|
+
version_requirements: *id009
|
147
|
+
- !ruby/object:Gem::Dependency
|
148
|
+
name: redgreen
|
149
|
+
prerelease: false
|
150
|
+
requirement: &id010 !ruby/object:Gem::Requirement
|
151
|
+
none: false
|
152
|
+
requirements:
|
153
|
+
- - ">="
|
154
|
+
- !ruby/object:Gem::Version
|
155
|
+
hash: 3
|
156
|
+
segments:
|
157
|
+
- 0
|
158
|
+
version: "0"
|
159
|
+
type: :development
|
160
|
+
version_requirements: *id010
|
105
161
|
description: The easiest way to check how many times a URL has been shared on Reddit, Digg, Twitter, Facebook, LinkedIn, GoogleBuzz and StumbleUpon!
|
106
162
|
email:
|
107
163
|
- vito@botta.name
|
@@ -112,18 +168,32 @@ extensions: []
|
|
112
168
|
extra_rdoc_files: []
|
113
169
|
|
114
170
|
files:
|
171
|
+
- .autotest
|
115
172
|
- .gitignore
|
116
173
|
- Gemfile
|
117
174
|
- Gemfile.lock
|
118
175
|
- README.rdoc
|
119
176
|
- Rakefile
|
177
|
+
- autotest/discover.rb
|
178
|
+
- autotest/rules.rb
|
120
179
|
- lib/share_counts.rb
|
180
|
+
- lib/share_counts/array.rb
|
121
181
|
- lib/share_counts/caching.rb
|
122
182
|
- lib/share_counts/common.rb
|
123
183
|
- lib/share_counts/reddit.rb
|
124
184
|
- share_counts.gemspec
|
125
|
-
-
|
126
|
-
-
|
185
|
+
- test/digg.json
|
186
|
+
- test/facebook.json
|
187
|
+
- test/googlebuzz.json
|
188
|
+
- test/linkedin.json
|
189
|
+
- test/reddit-by-domain.json
|
190
|
+
- test/reddit-url-info.json
|
191
|
+
- test/reddit.json
|
192
|
+
- test/stumbleupon.html
|
193
|
+
- test/test_helper.rb
|
194
|
+
- test/test_reddit_module.rb
|
195
|
+
- test/test_share_counts.rb
|
196
|
+
- test/twitter.json
|
127
197
|
has_rdoc: true
|
128
198
|
homepage: https://github.com/vitobotta/share_counts
|
129
199
|
licenses: []
|
@@ -154,10 +224,20 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
154
224
|
requirements: []
|
155
225
|
|
156
226
|
rubyforge_project: share_counts
|
157
|
-
rubygems_version: 1.
|
227
|
+
rubygems_version: 1.6.2
|
158
228
|
signing_key:
|
159
229
|
specification_version: 3
|
160
230
|
summary: The easiest way to check how many times a URL has been shared on Reddit, Digg, Twitter, Facebook, LinkedIn, GoogleBuzz and StumbleUpon!
|
161
231
|
test_files:
|
162
|
-
-
|
163
|
-
-
|
232
|
+
- test/digg.json
|
233
|
+
- test/facebook.json
|
234
|
+
- test/googlebuzz.json
|
235
|
+
- test/linkedin.json
|
236
|
+
- test/reddit-by-domain.json
|
237
|
+
- test/reddit-url-info.json
|
238
|
+
- test/reddit.json
|
239
|
+
- test/stumbleupon.html
|
240
|
+
- test/test_helper.rb
|
241
|
+
- test/test_reddit_module.rb
|
242
|
+
- test/test_share_counts.rb
|
243
|
+
- test/twitter.json
|
data/spec/share_count_spec.rb
DELETED
data/spec/test_helper.rb
DELETED
@@ -1,157 +0,0 @@
|
|
1
|
-
require 'rubygems'
|
2
|
-
gem "minitest"
|
3
|
-
require 'minitest/autorun'
|
4
|
-
|
5
|
-
require 'ansi'
|
6
|
-
|
7
|
-
class MiniTest::Unit
|
8
|
-
include ANSI::Code
|
9
|
-
|
10
|
-
PADDING_SIZE = 4
|
11
|
-
|
12
|
-
def run(args = [])
|
13
|
-
@verbose = true
|
14
|
-
|
15
|
-
filter = if args.first =~ /^(-n|--name)$/ then
|
16
|
-
args.shift
|
17
|
-
arg = args.shift
|
18
|
-
arg =~ /\/(.*)\// ? Regexp.new($1) : arg
|
19
|
-
else
|
20
|
-
/./ # anything - ^test_ already filtered by #tests
|
21
|
-
end
|
22
|
-
|
23
|
-
@@out.puts "Loaded suite #{$0.sub(/\.rb$/, '')}\nStarted"
|
24
|
-
|
25
|
-
start = Time.now
|
26
|
-
run_test_suites filter
|
27
|
-
|
28
|
-
@@out.puts
|
29
|
-
@@out.puts "Finished in #{'%.6f' % (Time.now - start)} seconds."
|
30
|
-
|
31
|
-
@@out.puts
|
32
|
-
|
33
|
-
@@out.print "%d tests, " % test_count
|
34
|
-
@@out.print "%d assertions, " % assertion_count
|
35
|
-
@@out.print red { "%d failures, " % failures }
|
36
|
-
@@out.print yellow { "%d errors, " % errors }
|
37
|
-
@@out.puts cyan { "%d skips" % skips}
|
38
|
-
|
39
|
-
return failures + errors if @test_count > 0 # or return nil...
|
40
|
-
end
|
41
|
-
|
42
|
-
# Overwrite #run_test_suites so that it prints out reports
|
43
|
-
# as errors are generated.
|
44
|
-
def run_test_suites(filter = /./)
|
45
|
-
@test_count, @assertion_count = 0, 0
|
46
|
-
old_sync, @@out.sync = @@out.sync, true if @@out.respond_to? :sync=
|
47
|
-
|
48
|
-
TestCase.test_suites.each do |suite|
|
49
|
-
test_cases = suite.test_methods.grep(filter)
|
50
|
-
if test_cases.size > 0
|
51
|
-
@@out.print "\n#{suite}:\n"
|
52
|
-
end
|
53
|
-
|
54
|
-
test_cases.each do |test|
|
55
|
-
inst = suite.new test
|
56
|
-
inst._assertions = 0
|
57
|
-
|
58
|
-
t = Time.now
|
59
|
-
|
60
|
-
@broken = nil
|
61
|
-
|
62
|
-
@@out.print(case inst.run(self)
|
63
|
-
when :pass
|
64
|
-
@broken = false
|
65
|
-
green { pad_with_size "PASS" }
|
66
|
-
when :error
|
67
|
-
@broken = true
|
68
|
-
yellow { pad_with_size "ERROR" }
|
69
|
-
when :fail
|
70
|
-
@broken = true
|
71
|
-
red { pad_with_size "FAIL" }
|
72
|
-
when :skip
|
73
|
-
@broken = false
|
74
|
-
cyan { pad_with_size "SKIP" }
|
75
|
-
end)
|
76
|
-
|
77
|
-
|
78
|
-
# @@out.print " #{test.humanize.gsub(/Test\s\d+\s(.*)/,"\\1")} "
|
79
|
-
@@out.print " #{test} "
|
80
|
-
@@out.print " (%.2fs) " % (Time.now - t)
|
81
|
-
|
82
|
-
if @broken
|
83
|
-
@@out.puts
|
84
|
-
|
85
|
-
report = @report.last
|
86
|
-
@@out.puts pad(report[:message], 10)
|
87
|
-
trace = MiniTest::filter_backtrace(report[:exception].backtrace).first
|
88
|
-
@@out.print pad(trace, 10)
|
89
|
-
|
90
|
-
@@out.puts
|
91
|
-
end
|
92
|
-
|
93
|
-
@@out.puts
|
94
|
-
@test_count += 1
|
95
|
-
@assertion_count += inst._assertions
|
96
|
-
end
|
97
|
-
end
|
98
|
-
@@out.sync = old_sync if @@out.respond_to? :sync=
|
99
|
-
[@test_count, @assertion_count]
|
100
|
-
end
|
101
|
-
|
102
|
-
def pad(str, size=PADDING_SIZE)
|
103
|
-
" " * size + str
|
104
|
-
end
|
105
|
-
|
106
|
-
def pad_with_size(str)
|
107
|
-
pad("%5s" % str)
|
108
|
-
end
|
109
|
-
|
110
|
-
# Overwrite #puke method so that is stores a hash
|
111
|
-
# with :message and :exception keys.
|
112
|
-
def puke(klass, meth, e)
|
113
|
-
result = nil
|
114
|
-
msg = case e
|
115
|
-
when MiniTest::Skip
|
116
|
-
@skips += 1
|
117
|
-
result = :skip
|
118
|
-
e.message
|
119
|
-
when MiniTest::Assertion
|
120
|
-
@failures += 1
|
121
|
-
result = :fail
|
122
|
-
e.message
|
123
|
-
else
|
124
|
-
@errors += 1
|
125
|
-
result = :error
|
126
|
-
"#{e.class}: #{e.message}\n"
|
127
|
-
end
|
128
|
-
|
129
|
-
@report << {:message => msg, :exception => e}
|
130
|
-
result
|
131
|
-
end
|
132
|
-
|
133
|
-
|
134
|
-
class TestCase
|
135
|
-
# Overwrite #run method so that is uses symbols
|
136
|
-
# as return values rather than characters.
|
137
|
-
def run(runner)
|
138
|
-
result = :pass
|
139
|
-
begin
|
140
|
-
@passed = nil
|
141
|
-
self.setup
|
142
|
-
self.send self.__name__
|
143
|
-
@passed = true
|
144
|
-
rescue Exception => e
|
145
|
-
@passed = false
|
146
|
-
result = runner.puke(self.class, self.__name__, e)
|
147
|
-
ensure
|
148
|
-
begin
|
149
|
-
self.teardown
|
150
|
-
rescue Exception => e
|
151
|
-
result = runner.puke(self.class, self.__name__, e)
|
152
|
-
end
|
153
|
-
end
|
154
|
-
result
|
155
|
-
end
|
156
|
-
end
|
157
|
-
end
|