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 CHANGED
@@ -4,3 +4,4 @@ coverage
4
4
  rdoc
5
5
  *.gem
6
6
  .bundle
7
+ Gemfile.lock
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
 
@@ -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")
@@ -1,3 +1,3 @@
1
1
  class GithubMetadata
2
- VERSION = "0.1.1"
2
+ VERSION = "0.2.0"
3
3
  end
@@ -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 contributors_url
103
- "https://github.com/#{user}/#{repo}/contributors"
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
- describe '#user' do
12
- specify do
13
- subject.user.should == 'aslakhellesoy'
14
- end
15
- end
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
- describe '#pull_requests' do
46
- specify do
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
- describe '#contributors' do
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
- describe '#contributor_usernames' do
60
- specify do
61
- subject.contributor_usernames.should include('aslakhellesoy')
62
- end
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
- describe '#contributor_realnames' do
66
- specify do
67
- subject.contributor_realnames.should include('Iain Hecker', 'Elliot Crosby-McCullough', 'Aslak Hellesøy')
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
- specify do
71
- subject.contributor_realnames.length.should < subject.contributor_usernames.length
72
- end
73
- end
31
+ its(:default_branch) { should == 'master' }
74
32
 
75
- describe '#contributor_names' do
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
- describe '#user' do
98
- specify do
99
- subject.user.should == 'colszowka'
100
- end
101
- end
43
+ its(:user) { should == 'colszowka' }
44
+ its(:repo) { should == 'simplecov' }
102
45
 
103
- describe '#repo' do
104
- specify do
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
- specify do
110
- subject.should_not have_wiki
111
- end
49
+ it { should have_issues }
50
+ its(:issues) { should == @raw.match(/Issues \((\d+)\)/)[1].to_i }
112
51
 
113
- describe '#wiki_pages' do
114
- specify do
115
- subject.wiki_pages.should be nil
116
- end
117
- end
52
+ its(:pull_requests) { should == 0 }
118
53
 
119
- specify do
120
- subject.should have_issues
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
- describe '#pull_requests' do
131
- specify do
132
- subject.pull_requests.should be 0
133
- end
134
- end
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
- describe '#contributors' do
137
- specify do
138
- expected = @raw.match(/(\d+) contributors/)[1].to_i
139
- subject.contributors.length.should be expected
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
- it 'should be all instance of Contributor' do
143
- subject.contributors.should be_all {|c| c.instance_of?(GithubMetadata::Contributor)}
144
- end
145
- end
146
-
147
- describe '#contributor_usernames' do
148
- specify do
149
- subject.contributor_usernames.should include('colszowka')
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
- describe '#contributor_realnames' do
154
- specify do
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
- describe '#contributor_names' do
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 be nil
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: false
5
- segments:
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-01-04 00:00:00 +01:00
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: rspec
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
- segments:
42
- - 2
43
- - 0
44
- - 0
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: *id002
59
+ version_requirements: *id004
48
60
  - !ruby/object:Gem::Dependency
49
61
  name: simplecov
50
62
  prerelease: false
51
- requirement: &id003 !ruby/object:Gem::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: *id003
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.7
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