github_metadata 0.1.1 → 0.2.0
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +1 -0
- data/README.rdoc +8 -0
- data/github_metadata.gemspec +5 -1
- data/lib/github_metadata/version.rb +1 -1
- data/lib/github_metadata.rb +55 -2
- data/spec/github_metadata_spec.rb +60 -129
- data/spec/spec_helper.rb +9 -0
- metadata +52 -25
- data/Gemfile.lock +0 -31
data/.gitignore
CHANGED
data/README.rdoc
CHANGED
@@ -11,6 +11,10 @@ The github_metadata gem is here to solve this. Currently it gives you, for any g
|
|
11
11
|
* The count of pull requests
|
12
12
|
* The list of contributors, with username and real name if available, and thereby also
|
13
13
|
the count of contributors
|
14
|
+
* The default branch
|
15
|
+
* The most recent commits
|
16
|
+
* The average date for recent commits (to analyze project activity - hat tip to Ryan Bates for the
|
17
|
+
idea!)
|
14
18
|
|
15
19
|
By the way: It goes very easy on the github servers in doing so, currently only performing 1 request
|
16
20
|
to fetch all of the information per instance.
|
@@ -36,6 +40,10 @@ Check out the full documentation to see all available methods.
|
|
36
40
|
|
37
41
|
* Better (any?) error handling
|
38
42
|
* Merge with my very own first_github_commit gem, which retrieves the very first commit to a repo
|
43
|
+
|
44
|
+
== Compatible rubies
|
45
|
+
|
46
|
+
Tested on MRI 1.8.7, 1.9.1, 1.9.2, and REE
|
39
47
|
|
40
48
|
== Note on Patches/Pull Requests
|
41
49
|
|
data/github_metadata.gemspec
CHANGED
@@ -15,8 +15,12 @@ Gem::Specification.new do |s|
|
|
15
15
|
s.rubyforge_project = "github_metadata"
|
16
16
|
|
17
17
|
s.add_dependency 'nokogiri'
|
18
|
+
s.add_dependency 'feedzirra'
|
19
|
+
s.add_dependency 'i18n'
|
18
20
|
s.add_development_dependency 'rspec', '>= 2.0.0'
|
19
|
-
s.add_development_dependency 'simplecov'
|
21
|
+
s.add_development_dependency 'simplecov', ">= 0.4.1"
|
22
|
+
s.add_development_dependency 'vcr'
|
23
|
+
s.add_development_dependency 'webmock'
|
20
24
|
|
21
25
|
s.files = `git ls-files`.split("\n")
|
22
26
|
s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
|
data/lib/github_metadata.rb
CHANGED
@@ -1,6 +1,8 @@
|
|
1
|
+
# encoding: utf-8
|
1
2
|
require 'rubygems'
|
2
3
|
require 'open-uri'
|
3
4
|
require 'nokogiri'
|
5
|
+
require 'feedzirra'
|
4
6
|
|
5
7
|
# A simple scraper that fetches data from github repos that is not
|
6
8
|
# available via the API. See README for an introduction and overview.
|
@@ -17,6 +19,26 @@ class GithubMetadata
|
|
17
19
|
end
|
18
20
|
end
|
19
21
|
|
22
|
+
# Object representation of a commit, initialized
|
23
|
+
# from a github repo commit feed entry
|
24
|
+
class Commit
|
25
|
+
attr_reader :title, :message, :committed_at, :url, :author
|
26
|
+
|
27
|
+
def initialize(atom_entry)
|
28
|
+
@atom_entry = atom_entry
|
29
|
+
@title = atom_entry.title
|
30
|
+
@message = atom_entry.content
|
31
|
+
@author = atom_entry.author
|
32
|
+
@committed_at = atom_entry.updated.kind_of?(Time) ? atom_entry.updated : Time.parse(atom_entry.updated)
|
33
|
+
@url = atom_entry.url
|
34
|
+
end
|
35
|
+
|
36
|
+
private
|
37
|
+
def atom_entry
|
38
|
+
@atom_entry
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
20
42
|
def initialize(user, repo)
|
21
43
|
@user, @repo = user, repo
|
22
44
|
end
|
@@ -36,6 +58,18 @@ class GithubMetadata
|
|
36
58
|
nil
|
37
59
|
end
|
38
60
|
|
61
|
+
def github_url
|
62
|
+
"https://github.com/#{user}/#{repo}/"
|
63
|
+
end
|
64
|
+
|
65
|
+
def contributors_url
|
66
|
+
File.join(github_url, 'contributors')
|
67
|
+
end
|
68
|
+
|
69
|
+
def commits_feed_url
|
70
|
+
File.join(github_url, "commits/#{default_branch}.atom")
|
71
|
+
end
|
72
|
+
|
39
73
|
# Returns an array of GithubMetadata::Contributor instances, one for each
|
40
74
|
# contributor listed on the contributors page of github
|
41
75
|
def contributors
|
@@ -91,6 +125,25 @@ class GithubMetadata
|
|
91
125
|
pull_request_link.text[/\d+/].to_i
|
92
126
|
end
|
93
127
|
|
128
|
+
# Returns the default branch of the repo
|
129
|
+
def default_branch
|
130
|
+
document.at_css('.tabs .contextswitch code').text
|
131
|
+
end
|
132
|
+
|
133
|
+
# Returns (at most) the last 20 commits (fetched from atom feed of the default_branch)
|
134
|
+
# as instances of GithubMetadata::Commit
|
135
|
+
def recent_commits
|
136
|
+
@recent_commits ||= commits_feed.entries.map {|e| GithubMetadata::Commit.new(e) }
|
137
|
+
end
|
138
|
+
|
139
|
+
# Returns the average date of recent commits (by default all (max 20), can be modified
|
140
|
+
# by giving the optional argument)
|
141
|
+
def average_recent_committed_at(num=100)
|
142
|
+
commit_times = recent_commits[0..num].map {|c| c.committed_at.to_f }
|
143
|
+
average_time = commit_times.inject(0) {|s, i| s + i} / commit_times.length
|
144
|
+
Time.at(average_time)
|
145
|
+
end
|
146
|
+
|
94
147
|
private
|
95
148
|
|
96
149
|
def document
|
@@ -99,8 +152,8 @@ class GithubMetadata
|
|
99
152
|
raise GithubMetadata::RepoNotFound, err.to_s
|
100
153
|
end
|
101
154
|
|
102
|
-
def
|
103
|
-
|
155
|
+
def commits_feed
|
156
|
+
@commits_feed ||= Feedzirra::Feed.fetch_and_parse(commits_feed_url)
|
104
157
|
end
|
105
158
|
|
106
159
|
def load_contributors
|
@@ -8,83 +8,29 @@ describe GithubMetadata do
|
|
8
8
|
end
|
9
9
|
subject { @metadata }
|
10
10
|
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
describe '#repo' do
|
18
|
-
specify do
|
19
|
-
subject.repo.should == 'cucumber'
|
20
|
-
end
|
21
|
-
end
|
22
|
-
|
23
|
-
specify do
|
24
|
-
subject.should have_wiki
|
25
|
-
end
|
26
|
-
|
27
|
-
describe '#wiki_pages' do
|
28
|
-
specify do
|
29
|
-
expected = @raw.match(/Wiki \((\d+)\)/)[1].to_i
|
30
|
-
expected.should be > 50
|
31
|
-
subject.wiki_pages.should == expected
|
32
|
-
end
|
33
|
-
end
|
34
|
-
|
35
|
-
specify do
|
36
|
-
subject.should_not have_issues
|
37
|
-
end
|
38
|
-
|
39
|
-
describe '#issues' do
|
40
|
-
specify do
|
41
|
-
subject.issues.should == nil
|
42
|
-
end
|
43
|
-
end
|
11
|
+
its(:user) { should == 'aslakhellesoy' }
|
12
|
+
its(:repo) { should == 'cucumber' }
|
13
|
+
|
14
|
+
specify { should have_wiki }
|
15
|
+
its(:wiki_pages) { should == @raw.match(/Wiki \((\d+)\)/)[1].to_i }
|
44
16
|
|
45
|
-
|
46
|
-
|
47
|
-
expected = @raw.match(/Pull Requests \((\d+)\)/)[1].to_i
|
48
|
-
subject.pull_requests.should == expected
|
49
|
-
end
|
50
|
-
end
|
17
|
+
specify { should_not have_issues }
|
18
|
+
its(:issues) { should be_nil }
|
51
19
|
|
52
|
-
|
53
|
-
specify do
|
54
|
-
expected = @raw.match(/(\d+) contributors/)[1].to_i
|
55
|
-
subject.contributors.length.should == expected
|
56
|
-
end
|
57
|
-
end
|
20
|
+
its(:pull_requests) { should == @raw.match(/Pull Requests \((\d+)\)/)[1].to_i }
|
58
21
|
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
end
|
22
|
+
its("contributors.length") { should == @raw.match(/(\d+) contributors/)[1].to_i}
|
23
|
+
its(:contributor_usernames) { should include('aslakhellesoy') }
|
24
|
+
its(:contributor_realnames) { should include('Iain Hecker', 'Elliot Crosby-McCullough', 'David Chelimsky') }
|
25
|
+
its("contributor_realnames.length") { should < @metadata.contributor_usernames.length }
|
64
26
|
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
end
|
27
|
+
its("contributor_names.length") { should == @metadata.contributors.count }
|
28
|
+
its(:contributor_names) { should include('Iain Hecker', 'Elliot Crosby-McCullough', 'David Chelimsky') }
|
29
|
+
its(:contributor_names) { should include('marocchino') }
|
69
30
|
|
70
|
-
|
71
|
-
subject.contributor_realnames.length.should < subject.contributor_usernames.length
|
72
|
-
end
|
73
|
-
end
|
31
|
+
its(:default_branch) { should == 'master' }
|
74
32
|
|
75
|
-
|
76
|
-
specify do
|
77
|
-
subject.contributor_names.count.should == subject.contributors.count
|
78
|
-
end
|
79
|
-
|
80
|
-
specify do
|
81
|
-
subject.contributor_names.should include('Iain Hecker', 'Elliot Crosby-McCullough', 'Aslak Hellesøy')
|
82
|
-
end
|
83
|
-
|
84
|
-
specify do
|
85
|
-
subject.contributor_names.should include('marocchino')
|
86
|
-
end
|
87
|
-
end
|
33
|
+
its(:commits_feed_url) { should == 'https://github.com/aslakhellesoy/cucumber/commits/master.atom' }
|
88
34
|
end
|
89
35
|
|
90
36
|
context "initialized with colszowka/simplecov" do
|
@@ -94,76 +40,55 @@ describe GithubMetadata do
|
|
94
40
|
end
|
95
41
|
subject { @metadata }
|
96
42
|
|
97
|
-
|
98
|
-
|
99
|
-
subject.user.should == 'colszowka'
|
100
|
-
end
|
101
|
-
end
|
43
|
+
its(:user) { should == 'colszowka' }
|
44
|
+
its(:repo) { should == 'simplecov' }
|
102
45
|
|
103
|
-
|
104
|
-
|
105
|
-
subject.repo.should == 'simplecov'
|
106
|
-
end
|
107
|
-
end
|
46
|
+
it { should_not have_wiki }
|
47
|
+
its(:wiki_pages) { should be_nil }
|
108
48
|
|
109
|
-
|
110
|
-
|
111
|
-
end
|
49
|
+
it { should have_issues }
|
50
|
+
its(:issues) { should == @raw.match(/Issues \((\d+)\)/)[1].to_i }
|
112
51
|
|
113
|
-
|
114
|
-
specify do
|
115
|
-
subject.wiki_pages.should be nil
|
116
|
-
end
|
117
|
-
end
|
52
|
+
its(:pull_requests) { should == 0 }
|
118
53
|
|
119
|
-
|
120
|
-
|
121
|
-
end
|
122
|
-
|
123
|
-
describe '#issues' do
|
124
|
-
specify do
|
125
|
-
expected = @raw.match(/Issues \((\d+)\)/)[1].to_i
|
126
|
-
subject.issues.should == expected
|
127
|
-
end
|
128
|
-
end
|
54
|
+
its("contributors.length") { should == @raw.match(/(\d+) contributors/)[1].to_i}
|
55
|
+
its(:contributors) { should be_all {|c| c.instance_of?(GithubMetadata::Contributor)} }
|
129
56
|
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
57
|
+
its(:contributor_usernames) { should include('colszowka') }
|
58
|
+
its(:contributor_realnames) { should include('Christoph Olszowka') }
|
59
|
+
its("contributor_names.count") { should == @metadata.contributors.count }
|
60
|
+
its(:contributor_names) { should include('Christoph Olszowka') }
|
61
|
+
|
62
|
+
its(:default_branch) { should == 'master' }
|
135
63
|
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
64
|
+
its(:commits_feed_url) { should == 'https://github.com/colszowka/simplecov/commits/master.atom' }
|
65
|
+
|
66
|
+
context "recent_commits" do
|
67
|
+
before(:all) do
|
68
|
+
VCR.use_cassette('simplecov') do
|
69
|
+
@metadata.recent_commits
|
70
|
+
end
|
140
71
|
end
|
72
|
+
subject { @metadata.recent_commits }
|
73
|
+
|
74
|
+
it { should have(20).items }
|
141
75
|
|
142
|
-
|
143
|
-
subject
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
|
76
|
+
context ".first" do
|
77
|
+
subject { @metadata.recent_commits.first }
|
78
|
+
|
79
|
+
it { should be_a(GithubMetadata::Commit)}
|
80
|
+
its(:title) { should == 'Require simplecov-html ~> 0.4.3' }
|
81
|
+
its(:author) { should == 'Christoph Olszowka' }
|
82
|
+
its(:url) { should == 'https://github.com/colszowka/simplecov/commit/5ec4ca01255135234fe5b771ec3dd743a2b4ea1a' }
|
83
|
+
its("committed_at.utc") { should == Time.mktime(2011, 2, 25, 16, 26, 16).utc }
|
150
84
|
end
|
151
85
|
end
|
152
86
|
|
153
|
-
|
154
|
-
|
155
|
-
subject.contributor_realnames.should include('Christoph Olszowka')
|
156
|
-
end
|
87
|
+
it("should return last commit date for average_recent_committed_at(1)") do
|
88
|
+
subject.average_recent_committed_at(1).should == Time.mktime(2011, 2, 25, 16, 25, 44).utc
|
157
89
|
end
|
158
90
|
|
159
|
-
|
160
|
-
specify do
|
161
|
-
subject.contributor_names.count.should be subject.contributors.count
|
162
|
-
end
|
163
|
-
specify do
|
164
|
-
subject.contributor_names.should include('Christoph Olszowka')
|
165
|
-
end
|
166
|
-
end
|
91
|
+
its("average_recent_committed_at.to_i") { should == Time.mktime(2011, 2, 11, 1, 49, 14).to_i }
|
167
92
|
end
|
168
93
|
|
169
94
|
context "initialized with an invalid repo path" do
|
@@ -179,7 +104,13 @@ describe GithubMetadata do
|
|
179
104
|
|
180
105
|
describe "fetch with invalid repo path" do
|
181
106
|
it "should return nil and swallow the 404" do
|
182
|
-
GithubMetadata.fetch('colszowka', 'anotherfunkyrepo').should
|
107
|
+
GithubMetadata.fetch('colszowka', 'anotherfunkyrepo').should be_nil
|
108
|
+
end
|
109
|
+
end
|
110
|
+
|
111
|
+
describe "fetch with valid repo path" do
|
112
|
+
it "should be a GithubMetadata instance" do
|
113
|
+
GithubMetadata.fetch('colszowka', 'simplecov').should be_a(GithubMetadata)
|
183
114
|
end
|
184
115
|
end
|
185
116
|
|
data/spec/spec_helper.rb
CHANGED
@@ -7,3 +7,12 @@ require 'github_metadata'
|
|
7
7
|
RSpec.configure do |config|
|
8
8
|
# some (optional) config here
|
9
9
|
end
|
10
|
+
|
11
|
+
require 'vcr'
|
12
|
+
|
13
|
+
VCR.config do |c|
|
14
|
+
c.allow_http_connections_when_no_cassette = true
|
15
|
+
c.default_cassette_options = { :record => :new_episodes }
|
16
|
+
c.cassette_library_dir = 'fixtures/vcr_cassettes'
|
17
|
+
c.stub_with :webmock # or :fakeweb
|
18
|
+
end
|
metadata
CHANGED
@@ -1,12 +1,8 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: github_metadata
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
prerelease:
|
5
|
-
|
6
|
-
- 0
|
7
|
-
- 1
|
8
|
-
- 1
|
9
|
-
version: 0.1.1
|
4
|
+
prerelease:
|
5
|
+
version: 0.2.0
|
10
6
|
platform: ruby
|
11
7
|
authors:
|
12
8
|
- Christoph Olszowka
|
@@ -14,7 +10,7 @@ autorequire:
|
|
14
10
|
bindir: bin
|
15
11
|
cert_chain: []
|
16
12
|
|
17
|
-
date: 2011-
|
13
|
+
date: 2011-03-11 00:00:00 +01:00
|
18
14
|
default_executable:
|
19
15
|
dependencies:
|
20
16
|
- !ruby/object:Gem::Dependency
|
@@ -25,39 +21,75 @@ dependencies:
|
|
25
21
|
requirements:
|
26
22
|
- - ">="
|
27
23
|
- !ruby/object:Gem::Version
|
28
|
-
segments:
|
29
|
-
- 0
|
30
24
|
version: "0"
|
31
25
|
type: :runtime
|
32
26
|
version_requirements: *id001
|
33
27
|
- !ruby/object:Gem::Dependency
|
34
|
-
name:
|
28
|
+
name: feedzirra
|
35
29
|
prerelease: false
|
36
30
|
requirement: &id002 !ruby/object:Gem::Requirement
|
37
31
|
none: false
|
38
32
|
requirements:
|
39
33
|
- - ">="
|
40
34
|
- !ruby/object:Gem::Version
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
35
|
+
version: "0"
|
36
|
+
type: :runtime
|
37
|
+
version_requirements: *id002
|
38
|
+
- !ruby/object:Gem::Dependency
|
39
|
+
name: i18n
|
40
|
+
prerelease: false
|
41
|
+
requirement: &id003 !ruby/object:Gem::Requirement
|
42
|
+
none: false
|
43
|
+
requirements:
|
44
|
+
- - ">="
|
45
|
+
- !ruby/object:Gem::Version
|
46
|
+
version: "0"
|
47
|
+
type: :runtime
|
48
|
+
version_requirements: *id003
|
49
|
+
- !ruby/object:Gem::Dependency
|
50
|
+
name: rspec
|
51
|
+
prerelease: false
|
52
|
+
requirement: &id004 !ruby/object:Gem::Requirement
|
53
|
+
none: false
|
54
|
+
requirements:
|
55
|
+
- - ">="
|
56
|
+
- !ruby/object:Gem::Version
|
45
57
|
version: 2.0.0
|
46
58
|
type: :development
|
47
|
-
version_requirements: *
|
59
|
+
version_requirements: *id004
|
48
60
|
- !ruby/object:Gem::Dependency
|
49
61
|
name: simplecov
|
50
62
|
prerelease: false
|
51
|
-
requirement: &
|
63
|
+
requirement: &id005 !ruby/object:Gem::Requirement
|
64
|
+
none: false
|
65
|
+
requirements:
|
66
|
+
- - ">="
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: 0.4.1
|
69
|
+
type: :development
|
70
|
+
version_requirements: *id005
|
71
|
+
- !ruby/object:Gem::Dependency
|
72
|
+
name: vcr
|
73
|
+
prerelease: false
|
74
|
+
requirement: &id006 !ruby/object:Gem::Requirement
|
52
75
|
none: false
|
53
76
|
requirements:
|
54
77
|
- - ">="
|
55
78
|
- !ruby/object:Gem::Version
|
56
|
-
segments:
|
57
|
-
- 0
|
58
79
|
version: "0"
|
59
80
|
type: :development
|
60
|
-
version_requirements: *
|
81
|
+
version_requirements: *id006
|
82
|
+
- !ruby/object:Gem::Dependency
|
83
|
+
name: webmock
|
84
|
+
prerelease: false
|
85
|
+
requirement: &id007 !ruby/object:Gem::Requirement
|
86
|
+
none: false
|
87
|
+
requirements:
|
88
|
+
- - ">="
|
89
|
+
- !ruby/object:Gem::Version
|
90
|
+
version: "0"
|
91
|
+
type: :development
|
92
|
+
version_requirements: *id007
|
61
93
|
description: Extracts additional information like amount of committers, issues and wiki pages from Github repos
|
62
94
|
email:
|
63
95
|
- christoph at olszowka de
|
@@ -71,7 +103,6 @@ files:
|
|
71
103
|
- .gitignore
|
72
104
|
- .rspec
|
73
105
|
- Gemfile
|
74
|
-
- Gemfile.lock
|
75
106
|
- LICENSE
|
76
107
|
- README.rdoc
|
77
108
|
- Rakefile
|
@@ -94,21 +125,17 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
94
125
|
requirements:
|
95
126
|
- - ">="
|
96
127
|
- !ruby/object:Gem::Version
|
97
|
-
segments:
|
98
|
-
- 0
|
99
128
|
version: "0"
|
100
129
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
101
130
|
none: false
|
102
131
|
requirements:
|
103
132
|
- - ">="
|
104
133
|
- !ruby/object:Gem::Version
|
105
|
-
segments:
|
106
|
-
- 0
|
107
134
|
version: "0"
|
108
135
|
requirements: []
|
109
136
|
|
110
137
|
rubyforge_project: github_metadata
|
111
|
-
rubygems_version: 1.3
|
138
|
+
rubygems_version: 1.5.3
|
112
139
|
signing_key:
|
113
140
|
specification_version: 3
|
114
141
|
summary: Extracts additional information from Github repos that isn't available via API
|
data/Gemfile.lock
DELETED
@@ -1,31 +0,0 @@
|
|
1
|
-
PATH
|
2
|
-
remote: .
|
3
|
-
specs:
|
4
|
-
github_metadata (0.1.0)
|
5
|
-
nokogiri
|
6
|
-
|
7
|
-
GEM
|
8
|
-
remote: http://rubygems.org/
|
9
|
-
specs:
|
10
|
-
diff-lcs (1.1.2)
|
11
|
-
nokogiri (1.4.4)
|
12
|
-
rspec (2.4.0)
|
13
|
-
rspec-core (~> 2.4.0)
|
14
|
-
rspec-expectations (~> 2.4.0)
|
15
|
-
rspec-mocks (~> 2.4.0)
|
16
|
-
rspec-core (2.4.0)
|
17
|
-
rspec-expectations (2.4.0)
|
18
|
-
diff-lcs (~> 1.1.2)
|
19
|
-
rspec-mocks (2.4.0)
|
20
|
-
simplecov (0.3.7)
|
21
|
-
simplecov-html (>= 0.3.7)
|
22
|
-
simplecov-html (0.3.9)
|
23
|
-
|
24
|
-
PLATFORMS
|
25
|
-
ruby
|
26
|
-
|
27
|
-
DEPENDENCIES
|
28
|
-
github_metadata!
|
29
|
-
nokogiri
|
30
|
-
rspec (>= 2.0.0)
|
31
|
-
simplecov
|