vanity-source 0.6 → 0.7
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/CHANGELOG +16 -0
- data/Gemfile +3 -1
- data/Rakefile +8 -1
- data/lib/vanity/source.rb +52 -6
- data/lib/vanity/sources/backtweets.rb +25 -20
- data/lib/vanity/sources/backtweets.yml +9 -0
- data/lib/vanity/sources/github.rb +44 -49
- data/lib/vanity/sources/github.yml +15 -0
- data/lib/vanity/sources/github_issues.rb +32 -29
- data/lib/vanity/sources/github_issues.yml +14 -0
- data/lib/vanity/sources/ruby_gems.rb +19 -22
- data/lib/vanity/sources/ruby_gems.yml +6 -0
- data/test/api_keys.yml +1 -0
- data/test/backtweets_test.rb +130 -0
- data/test/cassettes/backtweets.yml +63 -0
- data/test/cassettes/github.yml +121 -0
- data/test/cassettes/github_issues.yml +123 -0
- data/test/cassettes/ruby_gems.yml +36 -0
- data/test/github_issues_test.rb +161 -0
- data/test/github_test.rb +188 -0
- data/test/ruby_gems_test.rb +130 -0
- data/test/test.log +246 -0
- data/test/test_helper.rb +202 -0
- data/vanity-source.gemspec +6 -1
- metadata +71 -10
data/CHANGELOG
CHANGED
@@ -1,3 +1,19 @@
|
|
1
|
+
2010-08-24 v0.7 Added test suite and resources
|
2
|
+
|
3
|
+
Each source can have an associated resources file (YAML), e.g. ruby_gems.rb
|
4
|
+
would have ruby_gems.yml. The default implementation of methods like name,
|
5
|
+
description and display pull content from the resource. Resources can support
|
6
|
+
multiple languages, but we're starting with just EN.
|
7
|
+
|
8
|
+
Some sources also need API keys. These are accessed using the method api_key
|
9
|
+
that returns the API key value for the current source (often a string, but can
|
10
|
+
also be hash or array). There's an api_keys.yml file which contains fake API
|
11
|
+
keys. You can put a real key in there while generating a cassette for your test
|
12
|
+
case.
|
13
|
+
|
14
|
+
Test suite is here, using WebMock and VCR to rest API calls. Cassettes go in
|
15
|
+
test/cassettes, fixtures in test/fixtures.
|
16
|
+
|
1
17
|
2010-08-19 v0.6 Added Backtweet
|
2
18
|
|
3
19
|
2010-08-19 v0.5 Added Github and Github issues
|
data/Gemfile
CHANGED
data/Rakefile
CHANGED
@@ -1,4 +1,5 @@
|
|
1
1
|
require "rake/testtask"
|
2
|
+
require "shoulda/tasks"
|
2
3
|
|
3
4
|
# -- Building stuff --
|
4
5
|
|
@@ -25,4 +26,10 @@ task :push=>["test", "build"] do
|
|
25
26
|
sh "gem push #{spec.name}-#{spec.version}.gem"
|
26
27
|
end
|
27
28
|
|
28
|
-
|
29
|
+
|
30
|
+
task :default=>:test
|
31
|
+
Rake::TestTask.new do |task|
|
32
|
+
task.test_files = FileList['test/**/*_test.rb']
|
33
|
+
#task.warning = true
|
34
|
+
task.verbose = true
|
35
|
+
end
|
data/lib/vanity/source.rb
CHANGED
@@ -1,5 +1,8 @@
|
|
1
|
-
require "
|
1
|
+
require "active_support"
|
2
|
+
require "json"
|
2
3
|
require "net/http"
|
4
|
+
require "rack"
|
5
|
+
require "uri"
|
3
6
|
|
4
7
|
module Vanity
|
5
8
|
|
@@ -108,28 +111,49 @@ module Vanity
|
|
108
111
|
logger.info "Loaded source #{id}: #{klass}"
|
109
112
|
end
|
110
113
|
end
|
114
|
+
|
115
|
+
# API keys (see instance method api_key).
|
116
|
+
attr_accessor :api_keys
|
117
|
+
|
118
|
+
def included(klass)
|
119
|
+
klass.extend ClassMethods
|
120
|
+
end
|
111
121
|
end
|
112
122
|
|
123
|
+
|
124
|
+
module ClassMethods
|
125
|
+
# Source identifier.
|
126
|
+
def source_id
|
127
|
+
@source_id ||= name.demodulize.underscore
|
128
|
+
end
|
129
|
+
end
|
130
|
+
|
131
|
+
|
113
132
|
# Returns the display name for this source.
|
114
133
|
#
|
115
|
-
#
|
116
|
-
# Up".
|
134
|
+
# Uses the resource 'name', and fallbacks on the class name (e.g
|
135
|
+
# Source::OneUp becomes "One Up").
|
117
136
|
def name
|
118
|
-
|
137
|
+
resources["name"] || source_id.titleize
|
119
138
|
end
|
120
139
|
|
121
140
|
# Returns additional information about this source.
|
122
141
|
#
|
123
142
|
# A good description helps the user find this source and decide whether or
|
124
143
|
# not to use it.
|
144
|
+
#
|
145
|
+
# Uses the resource 'description'.
|
125
146
|
def description
|
147
|
+
resources["description"]
|
126
148
|
end
|
127
149
|
|
128
150
|
# This method returns a hash with two values:
|
129
151
|
# * inputs -- HTML fragment for a setup form
|
130
152
|
# * notes -- HTML fragment for setup notes
|
153
|
+
#
|
154
|
+
# Uses the resources 'inputs' and 'notes'.
|
131
155
|
def display
|
132
|
-
{}
|
156
|
+
{ :inputs=>resources["inputs"], :notes=>resources["notes"] }
|
133
157
|
end
|
134
158
|
|
135
159
|
# Called to setup a new collector with parameters from the HTML form (see
|
@@ -168,9 +192,31 @@ module Vanity
|
|
168
192
|
[]
|
169
193
|
end
|
170
194
|
|
195
|
+
protected
|
196
|
+
|
197
|
+
# Source identifier.
|
198
|
+
def source_id
|
199
|
+
self.class.source_id
|
200
|
+
end
|
201
|
+
|
171
202
|
# Logger. Dump messages that can help with troubleshooting.
|
172
203
|
def logger
|
173
|
-
|
204
|
+
Vanity::Source.logger
|
205
|
+
end
|
206
|
+
|
207
|
+
# Returns I18n resources for this gem.
|
208
|
+
def resources
|
209
|
+
unless @resources
|
210
|
+
file_name = File.dirname(__FILE__) + "/sources/#{source_id}.yml"
|
211
|
+
@resources = File.exist?(file_name) ? YAML.load_file(file_name)["en"] : {}
|
212
|
+
end
|
213
|
+
@resources
|
214
|
+
end
|
215
|
+
|
216
|
+
# Returns API key for this source. May return string or hash, depending on
|
217
|
+
# the API.
|
218
|
+
def api_key
|
219
|
+
@api_key ||= Vanity::Source.api_keys[source_id] or raise "No API key for #{source_id}"
|
174
220
|
end
|
175
221
|
end
|
176
222
|
end
|
@@ -4,20 +4,8 @@ module Vanity
|
|
4
4
|
class Backtweets
|
5
5
|
include Vanity::Source
|
6
6
|
|
7
|
-
INPUTS = <<-HTML
|
8
|
-
<label>URL <input type="text" name="source[url]" size="30"></label>
|
9
|
-
HTML
|
10
|
-
|
11
|
-
def description
|
12
|
-
"Finds Tweets for any URL"
|
13
|
-
end
|
14
|
-
|
15
|
-
def display
|
16
|
-
{ :inputs=>INPUTS }
|
17
|
-
end
|
18
|
-
|
19
7
|
def setup(context, params)
|
20
|
-
url = params["url"].strip
|
8
|
+
url = params["url"].strip.downcase.sub(/^http(s?):\/\//, "")
|
21
9
|
context["metric.name"] = "Tweets for #{url}"
|
22
10
|
context["metric.columns"] = [{ :id=>"tweets", :label=>"Tweets" }]
|
23
11
|
context["metric.totals"] = true
|
@@ -30,14 +18,31 @@ module Vanity
|
|
30
18
|
|
31
19
|
def update(context, webhook, &block)
|
32
20
|
Net::HTTP.start "backtweets.com" do |http|
|
33
|
-
get = Net::HTTP::Get.new("/search.json?key=#{
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
21
|
+
get = Net::HTTP::Get.new("/search.json?key=#{api_key}&q=#{Rack::Utils.escape context["url"]}")
|
22
|
+
response = http.request(get)
|
23
|
+
json = JSON.parse(response.body) rescue nil if Net::HTTPOK === response
|
24
|
+
unless json
|
25
|
+
logger.error "Backtweets: #{response.code} #{response.message}"
|
26
|
+
raise "Last request didn't go as expected, trying again later"
|
27
|
+
end
|
28
|
+
|
29
|
+
if tweets = json["tweets"]
|
30
|
+
last_tweet_id = context["last_tweet_id"]
|
31
|
+
if last_tweet_id
|
32
|
+
new_ones = tweets.take_while { |tweet| tweet["tweet_id"] != last_tweet_id }.reverse
|
33
|
+
new_ones.each do |tweet|
|
34
|
+
handle, id = tweet["tweet_from_user"], tweet["tweet_id"]
|
35
|
+
block.call :activity=>{ :id=>id, :body=>tweet["tweet_text"], :url=>"http://twitter.com/#{handle}/#{id}",
|
36
|
+
:timestamp=>Time.parse(tweet["tweet_created_at"]).utc,
|
37
|
+
:person=>{ :name=>handle, :url=>"http://twitter.com/#{handle}", :photo=>tweet["tweet_profile_image_url"] } }
|
38
|
+
end
|
39
|
+
end
|
40
|
+
if recent = tweets.first
|
41
|
+
context["last_tweet_id"] = recent["tweet_id"]
|
42
|
+
end
|
40
43
|
end
|
44
|
+
|
45
|
+
block.call :set=>{ :tweets=>json["totalresults"].to_i }
|
41
46
|
end
|
42
47
|
end
|
43
48
|
|
@@ -0,0 +1,9 @@
|
|
1
|
+
en:
|
2
|
+
description: "Finds Tweets linking to any URL"
|
3
|
+
inputs: |-
|
4
|
+
<label>URL <input type="text" name="source[url]" size="30"></label>
|
5
|
+
notes: |-
|
6
|
+
<ul>
|
7
|
+
<li>If this is a new search, it may take some time before we get the first results (including historical data). Check back in 8 hours.</li>
|
8
|
+
<li>Data provided by <a href="http://backtweets.com">Backtweets</a></li>
|
9
|
+
</ul>
|
@@ -4,45 +4,21 @@ module Vanity
|
|
4
4
|
class Github
|
5
5
|
include Vanity::Source
|
6
6
|
|
7
|
-
INPUTS = <<-HTML
|
8
|
-
<label>User/repository <input type="text" name="source[repo]" size="30"></label>
|
9
|
-
<label>Branch <input type="text" name="source[branch]" size="30" value="master"></label>
|
10
|
-
<p>For private repositories:</p>
|
11
|
-
<label>Username <input type="text" name="source[username]" size="30"></label>
|
12
|
-
<label><a href="https://github.com/account#admin_bucket" target="github">API Token</a> <input type="password" name="source[api_token]" size="30"></label>
|
13
|
-
HTML
|
14
|
-
|
15
|
-
NOTES = <<-HTML
|
16
|
-
<ul>
|
17
|
-
<li>Enter user/repository like this: <code>assaf/vanity</code></li>
|
18
|
-
<li>To access private repositories, authenticate using your username and API token.
|
19
|
-
You can find your API token in <a href="https://github.com/account#admin_bucket" target="github">Account Settings</a>.
|
20
|
-
</li>
|
21
|
-
</ul>
|
22
|
-
HTML
|
23
|
-
|
24
|
-
def description
|
25
|
-
"Tracks Github commits, watchers and forks"
|
26
|
-
end
|
27
|
-
|
28
|
-
def display
|
29
|
-
{ :inputs=>INPUTS, :notes=>NOTES }
|
30
|
-
end
|
31
|
-
|
32
7
|
def setup(context, params)
|
33
8
|
repo = params["repo"].strip
|
34
9
|
context["metric.name"] = "Github: #{repo}"
|
35
|
-
context["metric.columns"] = [{ :id=>"commits", :label=>"Commits" }, { :id=>"watchers", :label=>"Watchers" }, { :id=>"forks", :label=>"
|
10
|
+
context["metric.columns"] = [{ :id=>"commits", :label=>"Commits" }, { :id=>"watchers", :label=>"Watchers" }, { :id=>"forks", :label=>"Forks" }]
|
36
11
|
context["metric.totals"] = true
|
37
12
|
context["repo"] = repo
|
38
|
-
|
13
|
+
branch = params["branch"].strip
|
14
|
+
context["branch"] = branch.blank? ? "master" : branch
|
39
15
|
context["username"] = params["username"]
|
40
16
|
context["api_token"] = params["api_token"]
|
41
17
|
end
|
42
18
|
|
43
19
|
def validate(context)
|
44
|
-
raise "Missing
|
45
|
-
raise "
|
20
|
+
raise "Missing repository name" if context["repo"].blank?
|
21
|
+
raise "Need user name and repository name, e.g. assaf/vanity" unless context["repo"][/^[\w-]+\/[\w-]+$/]
|
46
22
|
end
|
47
23
|
|
48
24
|
def update(context, webhook, &block)
|
@@ -51,37 +27,52 @@ module Vanity
|
|
51
27
|
http.start do
|
52
28
|
case response = request(http, context, "/api/v2/json/repos/show/:repo")
|
53
29
|
when Net::HTTPOK
|
54
|
-
json = JSON.parse(response.body)["repository"]
|
55
|
-
forks = json["forks"]
|
56
|
-
watchers = json["watchers"]
|
57
|
-
block.call :set=>{ :forks=>forks, :watchers=>watchers }
|
58
|
-
context.update json.slice(*%w{description url homepage})
|
30
|
+
json = JSON.parse(response.body)["repository"] rescue nil
|
59
31
|
when Net::HTTPNotFound, Net::HTTPBadRequest
|
60
|
-
raise "Could not find the repository
|
32
|
+
raise "Could not find the repository #{context["repo"]}"
|
61
33
|
when Net::HTTPUnauthorized
|
62
34
|
raise "You are not authorized to access this repository, or invalid username/password"
|
35
|
+
end
|
36
|
+
if json
|
37
|
+
context.update json.slice(*%w{description url homepage})
|
38
|
+
block.call :set=>{ :forks=>json["forks"], :watchers=>json["watchers"] }
|
63
39
|
else
|
64
|
-
|
40
|
+
logger.error "Backtweets: #{response.code} #{response.message}"
|
41
|
+
error = true
|
65
42
|
end
|
66
43
|
|
67
44
|
case response = request(http, context, "/api/v2/json/commits/list/:repo/:branch")
|
68
45
|
when Net::HTTPOK
|
69
|
-
commits = JSON.parse(response.body)["commits"]
|
70
|
-
|
71
|
-
|
72
|
-
|
46
|
+
commits = JSON.parse(response.body)["commits"] rescue nil
|
47
|
+
when Net::HTTPNotFound, Net::HTTPBadRequest
|
48
|
+
raise "Could not find the branch #{context["branch"]}"
|
49
|
+
end
|
50
|
+
|
51
|
+
if commits
|
52
|
+
last_seen_id = context["last_commit"]["id"] if context["last_commit"]
|
53
|
+
if last_seen_id
|
54
|
+
new_ones = commits.take_while { |commit| commit["id"] != last_seen_id }
|
55
|
+
new_ones.reverse.each do |commit|
|
56
|
+
committer = commit["committer"]
|
57
|
+
block.call :activity=>{ :id=>commit["id"], :body=>commit["message"], :url=>commit["url"],
|
58
|
+
:timestamp=>Time.parse(commit["committed_date"]).utc,
|
59
|
+
:person=>{ :name=>committer["name"], :url=>"http://github.com/#{committer["name"]}",
|
60
|
+
:email=>committer["email"] } }
|
61
|
+
end
|
62
|
+
block.call :inc=>{ :commits=>new_ones.count }
|
73
63
|
else
|
74
64
|
block.call :set=>{ :commits=>commits.count }
|
75
65
|
end
|
76
66
|
if last_commit = commits.first
|
77
|
-
context
|
78
|
-
|
79
|
-
context["last_commit_at"] = last_commit["committed_date"]
|
67
|
+
context.update "last_commit"=>last_commit.slice("id", "message", "url").
|
68
|
+
merge("timestamp"=>Time.parse(last_commit["committed_date"]).utc)
|
80
69
|
end
|
81
|
-
|
82
|
-
|
70
|
+
else
|
71
|
+
logger.error "Backtweets: #{response.code} #{response.message}"
|
72
|
+
error = true
|
83
73
|
end
|
84
74
|
|
75
|
+
raise "Last request didn't go as expected, trying again later" if error
|
85
76
|
end
|
86
77
|
end
|
87
78
|
|
@@ -92,10 +83,14 @@ module Vanity
|
|
92
83
|
end
|
93
84
|
|
94
85
|
def meta(context)
|
95
|
-
[ { :title=>"Repository", :text=>context["repo"], :url=>context["url"] },
|
96
|
-
|
97
|
-
|
98
|
-
|
86
|
+
meta = [ { :title=>"Repository", :text=>context["repo"], :url=>context["url"] },
|
87
|
+
{ :title=>"Branch", :text=>context["branch"] },
|
88
|
+
{ :text=>context["description"] },
|
89
|
+
{ :title=>"Home page", :url=>context["homepage"] } ]
|
90
|
+
if last_commit = context["last_commit"]
|
91
|
+
meta << { :title=>"Commit", :text=>last_commit["message"], :url=>last_commit["url"] }
|
92
|
+
end
|
93
|
+
meta
|
99
94
|
end
|
100
95
|
end
|
101
96
|
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
en:
|
2
|
+
description: "Tracks Github commits, watchers and forks"
|
3
|
+
inputs: |-
|
4
|
+
<label>User/repository <input type="text" name="source[repo]" size="30"></label>
|
5
|
+
<label>Branch <input type="text" name="source[branch]" size="30" value="master"></label>
|
6
|
+
<p>For private repositories:</p>
|
7
|
+
<label>Username <input type="text" name="source[username]" size="30"></label>
|
8
|
+
<label><a href="https://github.com/account#admin_bucket" target="github">API Token</a> <input type="password" name="source[api_token]" size="30"></label>
|
9
|
+
notes: |-
|
10
|
+
<ul>
|
11
|
+
<li>Enter user name/repository like this: <code>assaf/vanity</code></li>
|
12
|
+
<li>To access private repositories, authenticate using your username and API token.
|
13
|
+
You can find your API token in <a href="https://github.com/account#admin_bucket" target="github">Account Settings</a>.
|
14
|
+
</li>
|
15
|
+
</ul>
|
@@ -4,34 +4,10 @@ module Vanity
|
|
4
4
|
class GithubIssues
|
5
5
|
include Vanity::Source
|
6
6
|
|
7
|
-
INPUTS = <<-HTML
|
8
|
-
<label>User/repository <input type="text" name="source[repo]" size="30"></label>
|
9
|
-
<p>For private repositories:</p>
|
10
|
-
<label>Username <input type="text" name="source[username]" size="30"></label>
|
11
|
-
<label><a href="https://github.com/account#admin_bucket" target="github">API Token</a> <input type="password" name="source[api_token]" size="30"></label>
|
12
|
-
HTML
|
13
|
-
|
14
|
-
NOTES = <<-HTML
|
15
|
-
<ul>
|
16
|
-
<li>Enter user/repository like this: <code>assaf/vanity</code></li>
|
17
|
-
<li>To access private repositories, authenticate using your username and API token.
|
18
|
-
You can find your API token in <a href="https://github.com/account#admin_bucket" target="github">Account Settings</a>.
|
19
|
-
</li>
|
20
|
-
</ul>
|
21
|
-
HTML
|
22
|
-
|
23
|
-
def description
|
24
|
-
"Tracks open/closed Github issues"
|
25
|
-
end
|
26
|
-
|
27
|
-
def display
|
28
|
-
{ :inputs=>INPUTS, :notes=>NOTES }
|
29
|
-
end
|
30
|
-
|
31
7
|
def setup(context, params)
|
32
8
|
repo = params["repo"].to_s.strip
|
33
9
|
context["metric.name"] = "Github Issues for #{repo}"
|
34
|
-
context["metric.columns"] = [{ :id=>"open", :label=>"
|
10
|
+
context["metric.columns"] = [{ :id=>"open", :label=>"Open issues" }, { :id=>"closed", :label=>"Closed issues" }]
|
35
11
|
context["metric.totals"] = true
|
36
12
|
context["repo"] = repo
|
37
13
|
context["username"] = params["username"]
|
@@ -40,27 +16,54 @@ module Vanity
|
|
40
16
|
|
41
17
|
def validate(context)
|
42
18
|
raise "Missing user/repository" if context["repo"].blank?
|
19
|
+
raise "Need user name and repository name, e.g. assaf/vanity" unless context["repo"][/^[\w-]+\/[\w-]+$/]
|
43
20
|
end
|
44
21
|
|
45
22
|
def update(context, webhook, &block)
|
46
23
|
http = Net::HTTP.new("github.com", 443)
|
47
24
|
http.use_ssl = true
|
48
25
|
http.start do
|
26
|
+
update = {}
|
49
27
|
case response = request(http, context, "open")
|
50
28
|
when Net::HTTPOK
|
51
|
-
open = JSON.parse(response.body)["issues"]
|
29
|
+
open = JSON.parse(response.body)["issues"] rescue nil
|
52
30
|
when Net::HTTPNotFound, Net::HTTPBadRequest
|
53
|
-
raise "Could not find the repository
|
31
|
+
raise "Could not find the repository #{context["repo"]}"
|
54
32
|
when Net::HTTPUnauthorized
|
55
33
|
raise "You are not authorized to access this repository, or invalid username/password"
|
56
34
|
end
|
35
|
+
if open
|
36
|
+
update[:open] = open.count
|
37
|
+
if open_ids = context["open-ids"]
|
38
|
+
open.reject { |issue| open_ids.include?(issue["number"]) }.reverse.each do |issue|
|
39
|
+
sha = Digest::SHA1.hexdigest([context["repo"], "open", issue["number"], issue["updated_at"]].join(":"))
|
40
|
+
block.call :activity=>{ :id=>sha, :body=>"New issue opened: #{issue["title"]}",
|
41
|
+
:url=>"http://github.com/#{context["repo"]}/issues#issue/#{issue["number"]}",
|
42
|
+
:timestamp=>Time.parse(issue["created_at"]).utc }
|
43
|
+
end
|
44
|
+
end
|
45
|
+
context["open-ids"] = open.map { |issue| issue["number"] }
|
46
|
+
end
|
57
47
|
|
58
48
|
case response = request(http, context, "closed")
|
59
49
|
when Net::HTTPOK
|
60
|
-
closed = JSON.parse(response.body)["issues"]
|
50
|
+
closed = JSON.parse(response.body)["issues"] rescue nil
|
51
|
+
end
|
52
|
+
if closed
|
53
|
+
update[:closed] = closed.count
|
54
|
+
if closed_ids = context["closed-ids"]
|
55
|
+
closed.reject { |issue| closed_ids.include?(issue["number"]) }.reverse.each do |issue|
|
56
|
+
sha = Digest::SHA1.hexdigest([context["repo"], "closed", issue["number"], issue["updated_at"]].join(":"))
|
57
|
+
block.call :activity=>{ :id=>sha, :body=>"Issue closed: #{issue["title"]}",
|
58
|
+
:url=>"http://github.com/#{context["repo"]}/issues#issue/#{issue["number"]}",
|
59
|
+
:timestamp=>Time.parse(issue["closed_at"]).utc }
|
60
|
+
end
|
61
|
+
end
|
62
|
+
context["closed-ids"] = closed.map { |issue| issue["number"] }
|
61
63
|
end
|
62
64
|
|
63
|
-
|
65
|
+
raise "Last request didn't go as expected, trying again later" if update.empty?
|
66
|
+
block.call :set=>update
|
64
67
|
end
|
65
68
|
end
|
66
69
|
|
@@ -0,0 +1,14 @@
|
|
1
|
+
en:
|
2
|
+
description: "Tracks open/closed Github issues"
|
3
|
+
inputs: |-
|
4
|
+
<label>User/repository <input type="text" name="source[repo]" size="30"></label>
|
5
|
+
<p>For private repositories:</p>
|
6
|
+
<label>Username <input type="text" name="source[username]" size="30"></label>
|
7
|
+
<label><a href="https://github.com/account#admin_bucket" target="github">API Token</a> <input type="password" name="source[api_token]" size="30"></label>
|
8
|
+
notes: |-
|
9
|
+
<ul>
|
10
|
+
<li>Enter user name/repository like this: <code>assaf/vanity</code></li>
|
11
|
+
<li>To access private repositories, authenticate using your username and API token.
|
12
|
+
You can find your API token in <a href="https://github.com/account#admin_bucket" target="github">Account Settings</a>.
|
13
|
+
</li>
|
14
|
+
</ul>
|
@@ -4,22 +4,6 @@ module Vanity
|
|
4
4
|
class RubyGems
|
5
5
|
include Vanity::Source
|
6
6
|
|
7
|
-
INPUTS = <<-HTML
|
8
|
-
<label>Gem name <input type="text" name="source[gem_name]" size="30"></label>
|
9
|
-
HTML
|
10
|
-
|
11
|
-
NOTES = <<-HTML
|
12
|
-
Gem names are case sensitive
|
13
|
-
HTML
|
14
|
-
|
15
|
-
def description
|
16
|
-
"Tracks activity (downloads and releases) of a Ruby Gem from rubygems.org"
|
17
|
-
end
|
18
|
-
|
19
|
-
def display
|
20
|
-
{ :inputs=>INPUTS, :notes=>NOTES }
|
21
|
-
end
|
22
|
-
|
23
7
|
def setup(context, params)
|
24
8
|
gem_name = params["gem_name"].to_s.strip
|
25
9
|
context["metric.name"] = "RubyGems: #{gem_name}"
|
@@ -33,16 +17,29 @@ module Vanity
|
|
33
17
|
end
|
34
18
|
|
35
19
|
def update(context, webhook)
|
36
|
-
|
20
|
+
gem_name = context["gem_name"]
|
21
|
+
uri = URI.parse("http://rubygems.org/api/v1/gems/#{Rack::Utils.escape gem_name}.json")
|
37
22
|
case response = Net::HTTP.get_response(uri)
|
38
23
|
when Net::HTTPOK
|
39
|
-
json = JSON.parse(response.body)
|
40
|
-
context["version"] = json["version"]
|
41
|
-
context.update json.slice(*%w{homepage_uri project_uri info authors info})
|
42
|
-
yield :set=>{ :downloads=>json["downloads"] }
|
24
|
+
json = JSON.parse(response.body) rescue nil
|
43
25
|
when Net::HTTPNotFound
|
44
|
-
raise "Could not find the Gem
|
26
|
+
raise "Could not find the Gem #{gem_name}"
|
27
|
+
end
|
28
|
+
unless json
|
29
|
+
logger.error "RubyGems: #{response.code} #{response.message}"
|
30
|
+
raise "Last request didn't go as expected, trying again later"
|
31
|
+
end
|
32
|
+
|
33
|
+
if context["version"]
|
34
|
+
current, latest = Gem::Version.new(context["version"]), Gem::Version.new(json["version"])
|
35
|
+
if latest > current
|
36
|
+
yield :activity=>{ :id=>"#{gem_name}-#{latest}", :url=>"http://rubygems.org/gems/#{gem_name}",
|
37
|
+
:body=>"Released <a href=\"http://rubygems.org/gems/#{gem_name}/versions/#{latest}\">#{gem_name} version #{latest}</a>" }
|
38
|
+
end
|
45
39
|
end
|
40
|
+
context["version"] = json["version"]
|
41
|
+
context.update json.slice(*%w{homepage_uri project_uri info authors info})
|
42
|
+
yield :set=>{ :downloads=>json["downloads"] }
|
46
43
|
end
|
47
44
|
|
48
45
|
def meta(context)
|
data/test/api_keys.yml
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
backtweets: "4554feeeeeeeeeeeeeee"
|