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 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