share_counts 0.0.2

Sign up to get free protection for your applications and to get access to all the features.
data/.gitignore ADDED
@@ -0,0 +1,3 @@
1
+ pkg/*
2
+ *.gem
3
+ .bundle
data/Gemfile ADDED
@@ -0,0 +1,2 @@
1
+ source "http://rubygems.org"
2
+ gemspec
data/Gemfile.lock ADDED
@@ -0,0 +1,32 @@
1
+ PATH
2
+ remote: .
3
+ specs:
4
+ share_counts (0.0.1)
5
+ json
6
+ nokogiri
7
+ redis
8
+ rest-client
9
+
10
+ GEM
11
+ remote: http://rubygems.org/
12
+ specs:
13
+ ansi (1.2.2)
14
+ json (1.5.1)
15
+ mime-types (1.16)
16
+ minitest (2.0.2)
17
+ nokogiri (1.4.4)
18
+ redis (2.1.1)
19
+ rest-client (1.6.1)
20
+ mime-types (>= 1.16)
21
+
22
+ PLATFORMS
23
+ ruby
24
+
25
+ DEPENDENCIES
26
+ ansi
27
+ json
28
+ minitest
29
+ nokogiri
30
+ redis
31
+ rest-client
32
+ share_counts!
data/README.rdoc ADDED
@@ -0,0 +1,173 @@
1
+ = Share Counts
2
+
3
+ This gem makes it super easy to check how many times a page/URL has been shared on social networks and aggregators.
4
+ It also supports caching with Redis both to speed things up and to reduce the risk of problems with API rate limits.
5
+
6
+ Services currently supported:
7
+
8
+ * Reddit
9
+ * Digg
10
+ * Twitter
11
+ * Facebook (Share * Like)
12
+ * LinkedIn
13
+ * Google Buzz
14
+ * StumbleUpon
15
+
16
+
17
+ == Installation
18
+
19
+ Just..
20
+ gem install share_counts
21
+
22
+ and then require/bundle it.
23
+
24
+ == Basic Usage
25
+
26
+ You can check each service individually...
27
+
28
+ ruby-1.9.2-p0 :001 > require "share_counts"
29
+ => true
30
+
31
+ ruby-1.9.2-p0 :002 > url = "http://vitobotta.com/awesomeprint-similar-production/"
32
+ => "http://vitobotta.com/awesomeprint-similar-production/"
33
+
34
+ # Reddit
35
+ ruby-1.9.2-p0 :003 > ShareCounts.reddit url
36
+ => 5
37
+
38
+ # Digg
39
+ ruby-1.9.2-p0 :004 > ShareCounts.digg url
40
+ => 1
41
+
42
+ # Twitter
43
+ ruby-1.9.2-p0 :005 > ShareCounts.twitter url
44
+ => 2
45
+
46
+ # Facebook shares
47
+ ruby-1.9.2-p0 :006 > ShareCounts.facebook url
48
+ => 1
49
+
50
+ # Facebook likes
51
+ ruby-1.9.2-p0 :007 > ShareCounts.fblike url
52
+ => 0
53
+
54
+ # LinkedIn
55
+ ruby-1.9.2-p0 :008 > ShareCounts.linkedin url
56
+ => 2
57
+
58
+ # StumbleUpon
59
+ ruby-1.9.2-p0 :009 > ShareCounts.stumbleupon url
60
+ => 0
61
+
62
+ # Google Buzz
63
+ ruby-1.9.2-p0 :010 > ShareCounts.googlebuzz url
64
+ => 0
65
+
66
+
67
+ or you can get 'em all in one shot:
68
+
69
+ ruby-1.9.2-p0 :017 > ShareCounts.all "http://vitobotta.com/awesomeprint-similar-production/"
70
+ => {:reddit=>5, :digg=>1, :twitter=>2, :facebook=>1, :fblike=>0, :linkedin=>2, :googlebuzz=>0, :stumbleupon=>0}
71
+
72
+
73
+ Additionally, for Facebook you can get shares and likes at once:
74
+
75
+ ruby-1.9.2-p0 :018 > ShareCounts.fball "http://vitobotta.com/awesomeprint-similar-production/"
76
+ => {"share_count"=>1, "like_count"=>0}
77
+
78
+
79
+ You can also specify which networks you want to query at once:
80
+
81
+ ruby-1.9.2-p0 :004 > ShareCounts.selected "http://vitobotta.com/awesomeprint-similar-production/", [:reddit, :twitter]
82
+ => {:reddit=>5, :twitter=>2}
83
+
84
+ Sometimes APIs may not be available or may be having issues (or there's some rate limit and you are making too many requests in a short time). When something goes wrong while querying a service, even for a max of 3 attempts, the share count for that service is left as set to nil. This way you can easily know whether a share count could be obtained or updated for a given URL, but simply checking if the share count is nil.
85
+
86
+ ruby-1.9.2-p0 :002 > ShareCounts.selected "http://vitobotta.com/awesomeprint-similar-production/", [:reddit, :twitter]
87
+ Making request to reddit...
88
+ Failed 1 attempt(s)
89
+ Failed 2 attempt(s)
90
+ Failed 3 attempt(s)
91
+ Something went wrong with reddit: can't convert nil into String
92
+ Making request to twitter...
93
+ => {:reddit=>nil, :twitter=>2}
94
+
95
+
96
+ == Caching
97
+
98
+ Depending on how and in which kind of applications you may want to use this gem, if you make too many requests in a short time some APIs may fail because of rate limits. Plus, some API may respond too slowly at times or just be down (Digg seems to be the least reliable of the group so far!). So the gem also supports caching, at the moment with Redis only.
99
+
100
+ It's very easy to enable and use the caching:
101
+
102
+ ruby-1.9.2-p0 :002 > require 'benchmark'
103
+ => true
104
+
105
+ # Enabling caching
106
+ ruby-1.9.2-p0 :003 > ShareCounts.use_cache
107
+ => #<Redis client v2.1.1 connected to redis://127.0.0.1:6379/0 (Redis v2.0.3)>
108
+
109
+
110
+ # First run, values are not cached
111
+ ruby-1.9.2-p0 :004 > Benchmark.realtime { ShareCounts.all "http://vitobotta.com/awesomeprint-similar-production/" }
112
+ Making request to reddit...
113
+ Making request to digg...
114
+ Making request to twitter...
115
+ Making request to facebook...
116
+ Making request to fblike...
117
+ Making request to linkedin...
118
+ Making request to googlebuzz...
119
+ Making request to stumbleupon...
120
+ => 3.7037899494171143
121
+
122
+
123
+ # Now values are cached
124
+ ruby-1.9.2-p0 :005 > Benchmark.realtime { ShareCounts.all "http://vitobotta.com/awesomeprint-similar-production/" }
125
+ Loaded reddit count from cache
126
+ Loaded digg count from cache
127
+ Loaded twitter count from cache
128
+ Loaded facebook count from cache
129
+ Loaded fblike count from cache
130
+ Loaded linkedin count from cache
131
+ Loaded googlebuzz count from cache
132
+ Loaded stumbleupon count from cache
133
+ => 0.003225088119506836
134
+
135
+
136
+ By default, the gem connects to the Redis store listening on 127.0.0.1:6379, but you can override these settings when you enable the caching:
137
+
138
+ ruby-1.9.2-p0 :002 > ShareCounts.use_cache :host => "192.168.10.85", :port => 7500
139
+ => #<Redis client v2.1.1 connected to redis://192.168.10.85:7500/0 (Redis v2.0.3)>
140
+
141
+ Or, if you are already using the "redis" gem in your application and therefore already have initialised a connection to the Redis store, you can either pass the reference to that instance:
142
+
143
+ ruby-1.9.2-p0 :003 > ShareCounts.use_cache :redis_store => YOUR_REFERENCE_TO_REDIS_STORE
144
+ => #<Redis client v2.1.1 connected to redis://127.0.0.1:6379/0 (Redis v2.0.3)>
145
+
146
+ or just set the global variable $share_counts_cache to that reference.
147
+
148
+ Similarly, by default cached share counts expire in two minutes, but you can override this by setting the global variable $share_counts_cache_expire (in seconds).
149
+
150
+ You can also get an hash having all the cached URLs with their share counts:
151
+
152
+ ruby-1.9.2-p0 :009 > ShareCounts.cached
153
+ => {"http://vitobotta.com/awesomeprint-similar-production/"=>{:fblike=>0, :stumbleupon=>0, :linkedin=>2, :googlebuzz=>0, :facebook=>1, :twitter=>2, :digg=>1, :reddit=>5}}
154
+
155
+
156
+ And, if needed, your can clear the cached values:
157
+
158
+ ruby-1.9.2-p0 :010 > ShareCounts.clear_cache
159
+ => ["ShareCounts||fblike||http://vitobotta.com/awesomeprint-similar-production/", "ShareCounts||stumbleupon||http://vitobotta.com/awesomeprint-similar-production/", "ShareCounts||linkedin||http://vitobotta.com/awesomeprint-similar-production/", "ShareCounts||googlebuzz||http://vitobotta.com/awesomeprint-similar-production/", "ShareCounts||facebook||http://vitobotta.com/awesomeprint-similar-production/", "ShareCounts||twitter||http://vitobotta.com/awesomeprint-similar-production/", "ShareCounts||digg||http://vitobotta.com/awesomeprint-similar-production/", "ShareCounts||reddit||http://vitobotta.com/awesomeprint-similar-production/"]
160
+ ruby-1.9.2-p0 :011 > ShareCounts.cached
161
+ => {
162
+
163
+ However the gem will namespace all the keys as you can see, in case you also use Redis for something else in the same app.
164
+
165
+ == TODO
166
+
167
+ * specs
168
+
169
+
170
+ == Authors
171
+
172
+ * Vito Botta ( http://vitobotta.com )
173
+
data/Rakefile ADDED
@@ -0,0 +1,2 @@
1
+ require 'bundler'
2
+ Bundler::GemHelper.install_tasks
@@ -0,0 +1,110 @@
1
+ module ShareCountsCaching
2
+
3
+ #
4
+ #
5
+ # Returns true if the Redis
6
+ # cache store has been initialised
7
+ #
8
+ #
9
+ def cache_enabled?
10
+ !$share_counts_cache.nil?
11
+ end
12
+
13
+
14
+
15
+ #
16
+ #
17
+ # Removes from Redis cache store all the keys
18
+ # used by ShareCounts.
19
+ #
20
+ #
21
+ def clear_cache
22
+ ($share_counts_cache || {}).keys.select{|cache_key| cache_key =~ /^ShareCounts/ }.each{|cache_key|
23
+ $share_counts_cache.del cache_key}
24
+ end
25
+
26
+
27
+ #
28
+ #
29
+ # Returns the cached share counts available for each URL, in the format
30
+ #
31
+ # {
32
+ # "URL 1": {
33
+ # :reddit => N,
34
+ # :digg => N,
35
+ # :twitter => N,
36
+ # :facebook => N,
37
+ # :fblike => N,
38
+ # :linkedin => N,
39
+ # :googlebuzz => N,
40
+ # :stumbleupon => N
41
+ # },
42
+ #
43
+ # "URL 2": {
44
+ # ...
45
+ # }
46
+ # }
47
+ #
48
+ #
49
+ def cached
50
+ urls = ($share_counts_cache || {}).keys.select{|k| k =~ /^ShareCounts/ }.inject({}) do |result, key|
51
+ data = key.split("||"); network = data[1]; url = data[2];
52
+ count = from_redis("ShareCounts||#{network}||#{url}")
53
+ (result[url] ||= {})[network.to_sym] = count unless ["all", "fball"].include? network
54
+ result
55
+ end
56
+ urls
57
+ end
58
+
59
+ #
60
+ #
61
+ # Enables caching with Redis.
62
+ #
63
+ # By default, it connects to 127.0.0.1:6379, but it is also
64
+ # possible to specify in the arguments :host, :port the
65
+ # connection details.
66
+ #
67
+ # If the application using this gem is already using Redis too,
68
+ # with the "redis" gem, it is possible to use the existing
69
+ # instance of Redis by either setting the :redist_store argument
70
+ # or by setting the global variable $share_counts_cache first.
71
+ #
72
+ #
73
+ def use_cache *args
74
+ arguments = args.inject({}) { |r, c| r.merge(c) }
75
+ $share_counts_cache ||= arguments[:redis_store] ||
76
+ Redis.new(:host => arguments[:host] || "127.0.0.1", :port => arguments[:port] || "6379")
77
+ end
78
+
79
+
80
+ private
81
+
82
+ #
83
+ #
84
+ # Caches the given value in Redis under the key specified.
85
+ # By default the value is cached for two minutes, but it
86
+ # is also possible to override this expiration time by
87
+ # setting the global variable $share_counts_cache_expire
88
+ # to a number of seconds.
89
+ #
90
+ #
91
+ def to_redis(cache_key, value)
92
+ $share_counts_cache.set cache_key, Marshal.dump(value)
93
+ $share_counts_cache.expire cache_key, $share_counts_cache_expire || 120
94
+ value
95
+ end
96
+
97
+
98
+ #
99
+ #
100
+ # Retrieves the value stores in Redis under
101
+ # the given key.
102
+ #
103
+ #
104
+ def from_redis(cache_key)
105
+ value = $share_counts_cache.get(cache_key)
106
+ return if value.nil?
107
+ Marshal.load value
108
+ end
109
+
110
+ end
@@ -0,0 +1,106 @@
1
+ module ShareCountsCommon
2
+
3
+ private
4
+
5
+ #
6
+ #
7
+ # Given the name of one of the supported social networks and a URL,
8
+ # attempts the execution of the given block to fetch the relevant share count.
9
+ #
10
+ # If caching with Redis is enabled, it will first try to
11
+ # fetch the share count from cache instead, if there is a valid
12
+ # cached value for the combination of network/URL. When a share count is
13
+ # instead retrieved with an HTTP request to the network's API and
14
+ # the caching with Redis is enabled, the value fetched is also cached.
15
+ #
16
+ # NOTE: caching will be skipped if the block fails.
17
+ #
18
+ #
19
+ def try service, url, &block
20
+ cache_key = "ShareCounts||#{service}||#{url}"
21
+ if cache_enabled?
22
+ if result = from_redis(cache_key)
23
+ puts "Loaded #{service} count from cache"
24
+ result
25
+ else
26
+ puts "Making request to #{service}..."
27
+ to_redis(cache_key, yield)
28
+ end
29
+ else
30
+ puts "Redis caching is disabled - Making request to #{service}..."
31
+ yield
32
+ end
33
+ rescue Exception => e
34
+ puts "Something went wrong with #{service}: #{e}"
35
+ end
36
+
37
+
38
+ #
39
+ #
40
+ # Performs an HTTP request to the given API URL with the specified params
41
+ # and within 2 seconds, and max 3 attempts
42
+ #
43
+ # If a :callback param is also specified, then it is assumed that the API
44
+ # returns a JSON text wrapped in a call to a method by that callback name,
45
+ # therefore in this case it manipulates the response to extract only
46
+ # the JSON data required.
47
+ #
48
+ def make_request *args
49
+ result = nil
50
+ attempts = 1
51
+
52
+ begin
53
+ timeout(2) do
54
+ url = args.shift
55
+ params = args.inject({}) { |r, c| r.merge! c }
56
+ response = RestClient.get url, { :params => params }
57
+
58
+
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
64
+ end
65
+
66
+ rescue Exception => e
67
+ puts "Failed #{attempts} attempt(s)"
68
+ attempts += 1
69
+ retry if attempts <= 3
70
+ end
71
+
72
+ result
73
+ end
74
+
75
+
76
+ #
77
+ #
78
+ # Makes an HTTP request with the given URL and params, and assumes
79
+ # that the response is in JSON format, therefore it returns
80
+ # the parsed JSON.
81
+ #
82
+ #
83
+ def from_json *args
84
+ JSON.parse make_request *args
85
+ end
86
+
87
+ #
88
+ #
89
+ # Most social networks' APIs returns normal JSON data;
90
+ # this method simply extracts directly the share count from
91
+ # the given JSON data, by following a pattern common to the
92
+ # structure of most JSON responses.
93
+ #
94
+ # It also requires a :selector argument that determines how
95
+ # to "query" the JSON data in a way that emulates XPATH,
96
+ # so to extract the share count.
97
+ #
98
+ #
99
+ def extract_count *args
100
+ json = args.shift
101
+ result = args.first.flatten.last.split("/").inject( json.is_a?(Array) ? json.first : json ) {
102
+ |r, c| r[c].is_a?(Array) ? r[c].first : r[c]
103
+ }
104
+ end
105
+
106
+ end
@@ -0,0 +1,90 @@
1
+ %w(rest_client json nokogiri redis timeout).each{|x| require x}
2
+
3
+ require File.expand_path(File.dirname(__FILE__) + "/share_counts/common")
4
+ require File.expand_path(File.dirname(__FILE__) + "/share_counts/caching")
5
+
6
+ module ShareCounts
7
+
8
+ extend ShareCountsCommon
9
+ extend ShareCountsCaching
10
+
11
+ def self.supported_networks
12
+ %w(reddit digg twitter facebook fblike linkedin googlebuzz stumbleupon)
13
+ end
14
+
15
+ def self.reddit url
16
+ try("reddit", url) {
17
+ extract_count from_json( "http://www.reddit.com/api/info.json", :url => url ),
18
+ :selector => "data/children/data/score"
19
+ }
20
+ end
21
+
22
+ def self.digg url
23
+ try("digg", url) {
24
+ extract_count from_json( "http://services.digg.com/2.0/story.getInfo", :links => url ),
25
+ :selector => "stories/diggs"
26
+ }
27
+ end
28
+
29
+ def self.twitter url
30
+ try("twitter", url) {
31
+ extract_count from_json( "http://urls.api.twitter.com/1/urls/count.json", :url => url, :callback => "x" ),
32
+ :selector => "count"
33
+ }
34
+ end
35
+
36
+ def self.facebook url
37
+ try("facebook", url) {
38
+ extract_count from_json("http://api.facebook.com/restserver.php", :v => "1.0", :method => "links.getStats",
39
+ :urls => url, :callback => "fb_sharepro_render", :format => "json" ), :selector => "share_count"
40
+ }
41
+ end
42
+
43
+ def self.fblike url
44
+ try("fblike", url) {
45
+ extract_count from_json("http://api.facebook.com/restserver.php", :v => "1.0", :method => "links.getStats",
46
+ :urls => url, :callback => "fb_sharepro_render", :format => "json" ), :selector => "like_count"
47
+ }
48
+ end
49
+
50
+ def self.fball url
51
+ try("fball", url) {
52
+ json = from_json("http://api.facebook.com/restserver.php", :v => "1.0",
53
+ :method => "links.getStats", :urls => url, :callback => "fb_sharepro_render", :format => "json"
54
+ ).first.select{ |k,v| ["share_count", "like_count"].include? k }
55
+ }
56
+ end
57
+
58
+ def self.linkedin url
59
+ try("linkedin", url) {
60
+ extract_count from_json("http://www.linkedin.com/cws/share-count",
61
+ :url => url, :callback => "IN.Tags.Share.handleCount" ), :selector => "count"
62
+ }
63
+ end
64
+
65
+ def self.googlebuzz url
66
+ try("googlebuzz", url) {
67
+ from_json("http://www.google.com/buzz/api/buzzThis/buzzCounter",
68
+ :url => url, :callback => "google_buzz_set_count" )[url]
69
+ }
70
+ end
71
+
72
+ def self.stumbleupon url
73
+ try("stumbleupon", url) {
74
+ Nokogiri::HTML.parse(
75
+ make_request("http://www.stumbleupon.com/badge/embed/5/", :url => url )
76
+ ).xpath( "//body/div/ul/li[2]/a/span").text.to_i
77
+ }
78
+ end
79
+
80
+ def self.all url
81
+ supported_networks.inject({}) { |r, c| r[c.to_sym] = ShareCounts.send(c, url); r }
82
+ end
83
+
84
+ def self.selected url, selections
85
+ selections.map{|name| name.downcase}.select{|name| supported_networks.include? name.to_s}.inject({}) {
86
+ |r, c| r[c.to_sym] = ShareCounts.send(c, url); r }
87
+ end
88
+
89
+ end
90
+
@@ -0,0 +1,29 @@
1
+ # -*- encoding: utf-8 -*-
2
+ $:.push File.expand_path("../lib", __FILE__)
3
+ $:.push File.expand_path("../lib/share_counts", __FILE__)
4
+
5
+ Gem::Specification.new do |s|
6
+ s.name = "share_counts"
7
+ s.version = "0.0.2"
8
+ s.platform = Gem::Platform::RUBY
9
+ s.authors = ["Vito Botta"]
10
+ s.email = ["vito@botta.name"]
11
+ s.homepage = "https://github.com/vitobotta/share_counts"
12
+ s.summary = %q{The easiest way to check how many times a URL has been shared on Reddit, Digg, Twitter, Facebook, LinkedIn, GoogleBuzz and StumbleUpon!}
13
+ s.description = %q{The easiest way to check how many times a URL has been shared on Reddit, Digg, Twitter, Facebook, LinkedIn, GoogleBuzz and StumbleUpon!}
14
+
15
+ s.add_dependency "rest-client"
16
+ s.add_dependency "json"
17
+ s.add_dependency "nokogiri"
18
+ s.add_dependency "redis"
19
+
20
+ s.add_development_dependency "minitest"
21
+ s.add_development_dependency "ansi"
22
+
23
+ s.rubyforge_project = "share_counts"
24
+
25
+ s.files = `git ls-files`.split("\n")
26
+ s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
27
+ s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
28
+ s.require_paths = ["lib"]
29
+ end
@@ -0,0 +1,3 @@
1
+ require File.expand_path(File.join(File.dirname(__FILE__), "test_helper"))
2
+
3
+ # nothing in here yet!
@@ -0,0 +1,157 @@
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
metadata ADDED
@@ -0,0 +1,153 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: share_counts
3
+ version: !ruby/object:Gem::Version
4
+ prerelease: false
5
+ segments:
6
+ - 0
7
+ - 0
8
+ - 2
9
+ version: 0.0.2
10
+ platform: ruby
11
+ authors:
12
+ - Vito Botta
13
+ autorequire:
14
+ bindir: bin
15
+ cert_chain: []
16
+
17
+ date: 2011-01-30 00:00:00 +00:00
18
+ default_executable:
19
+ dependencies:
20
+ - !ruby/object:Gem::Dependency
21
+ name: rest-client
22
+ prerelease: false
23
+ requirement: &id001 !ruby/object:Gem::Requirement
24
+ none: false
25
+ requirements:
26
+ - - ">="
27
+ - !ruby/object:Gem::Version
28
+ segments:
29
+ - 0
30
+ version: "0"
31
+ type: :runtime
32
+ version_requirements: *id001
33
+ - !ruby/object:Gem::Dependency
34
+ name: json
35
+ prerelease: false
36
+ requirement: &id002 !ruby/object:Gem::Requirement
37
+ none: false
38
+ requirements:
39
+ - - ">="
40
+ - !ruby/object:Gem::Version
41
+ segments:
42
+ - 0
43
+ version: "0"
44
+ type: :runtime
45
+ version_requirements: *id002
46
+ - !ruby/object:Gem::Dependency
47
+ name: nokogiri
48
+ prerelease: false
49
+ requirement: &id003 !ruby/object:Gem::Requirement
50
+ none: false
51
+ requirements:
52
+ - - ">="
53
+ - !ruby/object:Gem::Version
54
+ segments:
55
+ - 0
56
+ version: "0"
57
+ type: :runtime
58
+ version_requirements: *id003
59
+ - !ruby/object:Gem::Dependency
60
+ name: redis
61
+ prerelease: false
62
+ requirement: &id004 !ruby/object:Gem::Requirement
63
+ none: false
64
+ requirements:
65
+ - - ">="
66
+ - !ruby/object:Gem::Version
67
+ segments:
68
+ - 0
69
+ version: "0"
70
+ type: :runtime
71
+ version_requirements: *id004
72
+ - !ruby/object:Gem::Dependency
73
+ name: minitest
74
+ prerelease: false
75
+ requirement: &id005 !ruby/object:Gem::Requirement
76
+ none: false
77
+ requirements:
78
+ - - ">="
79
+ - !ruby/object:Gem::Version
80
+ segments:
81
+ - 0
82
+ version: "0"
83
+ type: :development
84
+ version_requirements: *id005
85
+ - !ruby/object:Gem::Dependency
86
+ name: ansi
87
+ prerelease: false
88
+ requirement: &id006 !ruby/object:Gem::Requirement
89
+ none: false
90
+ requirements:
91
+ - - ">="
92
+ - !ruby/object:Gem::Version
93
+ segments:
94
+ - 0
95
+ version: "0"
96
+ type: :development
97
+ version_requirements: *id006
98
+ description: The easiest way to check how many times a URL has been shared on Reddit, Digg, Twitter, Facebook, LinkedIn, GoogleBuzz and StumbleUpon!
99
+ email:
100
+ - vito@botta.name
101
+ executables: []
102
+
103
+ extensions: []
104
+
105
+ extra_rdoc_files: []
106
+
107
+ files:
108
+ - .gitignore
109
+ - Gemfile
110
+ - Gemfile.lock
111
+ - README.rdoc
112
+ - Rakefile
113
+ - lib/share_counts.rb
114
+ - lib/share_counts/caching.rb
115
+ - lib/share_counts/common.rb
116
+ - share_counts.gemspec
117
+ - spec/share_count_spec.rb
118
+ - spec/test_helper.rb
119
+ has_rdoc: true
120
+ homepage: https://github.com/vitobotta/share_counts
121
+ licenses: []
122
+
123
+ post_install_message:
124
+ rdoc_options: []
125
+
126
+ require_paths:
127
+ - lib
128
+ required_ruby_version: !ruby/object:Gem::Requirement
129
+ none: false
130
+ requirements:
131
+ - - ">="
132
+ - !ruby/object:Gem::Version
133
+ segments:
134
+ - 0
135
+ version: "0"
136
+ required_rubygems_version: !ruby/object:Gem::Requirement
137
+ none: false
138
+ requirements:
139
+ - - ">="
140
+ - !ruby/object:Gem::Version
141
+ segments:
142
+ - 0
143
+ version: "0"
144
+ requirements: []
145
+
146
+ rubyforge_project: share_counts
147
+ rubygems_version: 1.3.7
148
+ signing_key:
149
+ specification_version: 3
150
+ summary: The easiest way to check how many times a URL has been shared on Reddit, Digg, Twitter, Facebook, LinkedIn, GoogleBuzz and StumbleUpon!
151
+ test_files:
152
+ - spec/share_count_spec.rb
153
+ - spec/test_helper.rb