github_metadata 0.1.1 → 0.2.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/.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
|