contributions 0.1.1
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +20 -0
- data/.travis.yml +4 -0
- data/Gemfile +4 -0
- data/LICENSE +22 -0
- data/README.md +162 -0
- data/Rakefile +16 -0
- data/TODO.markdown +6 -0
- data/contributions.gemspec +22 -0
- data/lib/contributions/contributions.rb +153 -0
- data/lib/contributions/git.rb +66 -0
- data/lib/contributions/github_api.rb +62 -0
- data/lib/contributions/repository_list.rb +56 -0
- data/lib/contributions/string_utils.rb +133 -0
- data/lib/contributions/version.rb +3 -0
- data/lib/contributions.rb +9 -0
- data/test/contributions_integration_test.rb +78 -0
- data/test/contributions_test.rb +112 -0
- data/test/git_test.rb +28 -0
- data/test/github_api_test.rb +41 -0
- data/test/repository_list_test.rb +73 -0
- data/test/string_utils_test.rb +62 -0
- data/test/teststrap.rb +2 -0
- metadata +108 -0
data/.gitignore
ADDED
data/.travis.yml
ADDED
data/Gemfile
ADDED
data/LICENSE
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2012 Charlie Tanksley
|
2
|
+
|
3
|
+
MIT License
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
6
|
+
a copy of this software and associated documentation files (the
|
7
|
+
"Software"), to deal in the Software without restriction, including
|
8
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
9
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
10
|
+
permit persons to whom the Software is furnished to do so, subject to
|
11
|
+
the following conditions:
|
12
|
+
|
13
|
+
The above copyright notice and this permission notice shall be
|
14
|
+
included in all copies or substantial portions of the Software.
|
15
|
+
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
17
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
18
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
19
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
20
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
21
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
22
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,162 @@
|
|
1
|
+
# Contributions
|
2
|
+
|
3
|
+
Get the detailed information about all your OSS contributions in one
|
4
|
+
place.
|
5
|
+
|
6
|
+
## Installation
|
7
|
+
|
8
|
+
Add this line to your application's Gemfile:
|
9
|
+
|
10
|
+
gem 'contributions'
|
11
|
+
|
12
|
+
And then execute:
|
13
|
+
|
14
|
+
$ bundle
|
15
|
+
|
16
|
+
Or install it yourself as:
|
17
|
+
|
18
|
+
$ gem install contributions
|
19
|
+
|
20
|
+
## Fair Warning:
|
21
|
+
|
22
|
+
Right now, Contributions doesn't know what to do if you have more than
|
23
|
+
100 public repositories. I'll fix that later. Consider yourself
|
24
|
+
warned.
|
25
|
+
|
26
|
+
## Requirements
|
27
|
+
|
28
|
+
[![Build Status](https://secure.travis-ci.org/charlietanksley/contributions.png)](http://travis-ci.org/charlietanksley/contributions)
|
29
|
+
|
30
|
+
Contributions is known to work on:
|
31
|
+
|
32
|
+
* MRI 1.9.2
|
33
|
+
* MRI 1.9.3
|
34
|
+
|
35
|
+
At present it does not work on:
|
36
|
+
|
37
|
+
* Rubinius 1.2.4
|
38
|
+
|
39
|
+
I don't know about any other Rubys.
|
40
|
+
|
41
|
+
## Usage
|
42
|
+
|
43
|
+
### Finding contributions
|
44
|
+
|
45
|
+
The main idea behind contributions is to make it easy to add your open
|
46
|
+
source contributions to a resume or website.
|
47
|
+
|
48
|
+
A Contributions object takes a hash as an argument. The only required
|
49
|
+
key is `:username`. This is the user's github username. From here you
|
50
|
+
have a few options. You can manually update the list of repositories
|
51
|
+
(see below). When you are ready you can use the
|
52
|
+
`.contributions_as_hash` method to return the user's contributions.
|
53
|
+
|
54
|
+
Finding all a user's contributions is very computationally intensive: we
|
55
|
+
actually create a clone (in a temporary directory :)) of every forked
|
56
|
+
repository and look for contributions via a `git log --author=username`
|
57
|
+
command. The first time you call this method it may take a while to
|
58
|
+
return the hash (especially if you have forks of really big projects).
|
59
|
+
Since you might want to access this hash multiple times, the result is
|
60
|
+
cached. If you want or need to update this for some reason, use the
|
61
|
+
`.reload_contributions` method and then `.contributions_as_hash` to get
|
62
|
+
the new results.
|
63
|
+
|
64
|
+
### But which projects?
|
65
|
+
|
66
|
+
By default, contributions assumes that a user has contributed to every
|
67
|
+
forked OSS project in his or her github account. When you generate your
|
68
|
+
list of contributions, contributions will look for contributions in
|
69
|
+
every forked repository in your account. (Note: it will look not for
|
70
|
+
additions you have made locally, but for additions that have been merged
|
71
|
+
into the forked project; it looks for commits in that project for which
|
72
|
+
you are either an author or committer.)
|
73
|
+
|
74
|
+
Some people have lots of forks that they don't contribute to. In that
|
75
|
+
case, you can pass an array of projects to ignore to contributions:
|
76
|
+
|
77
|
+
Contributions::Contributions.new(:username => 'u', :remove => ['homebrew'])
|
78
|
+
|
79
|
+
If you have contributed to projects that you have not forked (perhaps
|
80
|
+
you keep a tidy github account :)), you can add those in by passing an
|
81
|
+
array of projects to contributions:
|
82
|
+
|
83
|
+
Contributions::Contributions.new(:username => 'u', :add => ['rubinius/rubinius'])
|
84
|
+
|
85
|
+
Notice that you must pass both the repository name and the username in
|
86
|
+
this case.
|
87
|
+
|
88
|
+
Finally, you might want to only get your contributions to a certain set
|
89
|
+
of repositories, ignoring any others (e.g., any others your forked).
|
90
|
+
|
91
|
+
Contributions::Contributions.new(:username => 'u', :only => ['rubinius/rubinius'])
|
92
|
+
|
93
|
+
The envisioned use case for this command is when you have lots of forked
|
94
|
+
repositories, perhaps even ones you have contributed to, but only care
|
95
|
+
to get your contributions for one.
|
96
|
+
|
97
|
+
### Adding or subtracting contributions
|
98
|
+
|
99
|
+
Before you determine the contributions for a user, you might need to
|
100
|
+
alter the list of repositories you are looking at. You can find out
|
101
|
+
which repositories are currently being considered with the
|
102
|
+
`repositories` method:
|
103
|
+
|
104
|
+
c = Contributions::Contributions.new(:username => 'u')
|
105
|
+
c.repositories
|
106
|
+
# ['rubinius/rubinius', 'mxcl/homebrew']
|
107
|
+
|
108
|
+
You can find out just the project names with the `project_names` method
|
109
|
+
|
110
|
+
c = Contributions::Contributions.new(:username => 'u')
|
111
|
+
c.project_names
|
112
|
+
# ['rubinius', 'homebrew']
|
113
|
+
|
114
|
+
You can subtract a repository using the `remove` method. The `remove`
|
115
|
+
method takes either a string (in the case of a single repository) or an
|
116
|
+
array of strings as an argument. The repositories should be specified
|
117
|
+
in the username/repository pattern.
|
118
|
+
|
119
|
+
c = Contributions::Contributions.new(:username => 'u')
|
120
|
+
c.repositories
|
121
|
+
# ['sinatra/sinatra', 'rubinius/rubinius', 'mxcl/homebrew']
|
122
|
+
c.remove('sinatra/sinatra')
|
123
|
+
# ['rubinius/rubinius', 'mxcl/homebrew']
|
124
|
+
c.remove(['rubinius/rubinius', 'mxcl/homebrew'])
|
125
|
+
# []
|
126
|
+
|
127
|
+
You can add a repository using the `add` command. It takes arguments
|
128
|
+
just like `remove`.
|
129
|
+
|
130
|
+
c = Contributions::Contributions.new(:username => 'u')
|
131
|
+
c.repositories
|
132
|
+
# []
|
133
|
+
c.add('sinatra/sinatra')
|
134
|
+
# ['sinatra/sinatra']
|
135
|
+
c.add(['rubinius/rubinius', 'mxcl/homebrew'])
|
136
|
+
# ['sinatra/sinatra', 'rubinius/rubinius', 'mxcl/homebrew']
|
137
|
+
|
138
|
+
### Determining your contributions
|
139
|
+
|
140
|
+
c = Contributions::Contributions.new(:username => 'u')
|
141
|
+
# => # does not determine the contributions
|
142
|
+
c.contributions_as_hash
|
143
|
+
# => {:project1 => [...], :project2 => [...] ... }
|
144
|
+
|
145
|
+
### Getting the information out
|
146
|
+
|
147
|
+
You access commits via `contributions_as_hash`:
|
148
|
+
|
149
|
+
c = Contributions::Contributions.new(:username => 'u')
|
150
|
+
c.repositories
|
151
|
+
# ['sinatra/sinatra', 'rubinius/rubinius']
|
152
|
+
c.contributions_as_hash
|
153
|
+
# => {:'sinatra/sinatra' => ["commit data", "commit data"], :'rubinius/rubinius' => ["commit data"]}
|
154
|
+
|
155
|
+
|
156
|
+
## Contributing
|
157
|
+
|
158
|
+
1. Fork it
|
159
|
+
2. Create your feature branch (`git checkout -b my-new-feature`)
|
160
|
+
3. Commit your changes (`git commit -am 'Added some feature'`)
|
161
|
+
4. Push to the branch (`git push origin my-new-feature`)
|
162
|
+
5. Create new Pull Request
|
data/Rakefile
ADDED
@@ -0,0 +1,16 @@
|
|
1
|
+
#!/usr/bin/env rake
|
2
|
+
require "bundler/gem_tasks"
|
3
|
+
|
4
|
+
require 'rake'
|
5
|
+
require 'rake/testtask'
|
6
|
+
|
7
|
+
desc "Run all our tests"
|
8
|
+
task :test do
|
9
|
+
Rake::TestTask.new do |t|
|
10
|
+
t.libs << "test"
|
11
|
+
t.pattern = "test/**/*_test.rb"
|
12
|
+
t.verbose = false
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
task :default => :test
|
data/TODO.markdown
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
require File.expand_path('../lib/contributions/version', __FILE__)
|
3
|
+
|
4
|
+
Gem::Specification.new do |gem|
|
5
|
+
gem.authors = ["Charlie Tanksley"]
|
6
|
+
gem.email = ["charlie.tanksley@gmail.com"]
|
7
|
+
gem.description = %q{Gather your contributions to OSS projects (hosted on github!).}
|
8
|
+
gem.summary = %q{A gem to find all your contributions to OSS projects on github and make that information easy to access.}
|
9
|
+
gem.homepage = ""
|
10
|
+
|
11
|
+
gem.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
|
12
|
+
gem.files = `git ls-files`.split("\n")
|
13
|
+
gem.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
|
14
|
+
gem.name = "contributions"
|
15
|
+
gem.require_paths = ["lib"]
|
16
|
+
gem.version = Contributions::VERSION
|
17
|
+
|
18
|
+
gem.add_dependency('json')
|
19
|
+
|
20
|
+
gem.add_development_dependency('riot')
|
21
|
+
gem.add_development_dependency('rake')
|
22
|
+
end
|
@@ -0,0 +1,153 @@
|
|
1
|
+
require 'json'
|
2
|
+
|
3
|
+
module Contributions
|
4
|
+
class Contributions
|
5
|
+
|
6
|
+
# opts - a Hash with, at the very least, a username. Optional
|
7
|
+
# arguments include :remove (to ignore some repository),
|
8
|
+
# :add (to add), and :only (to focus).
|
9
|
+
def initialize(opts={})
|
10
|
+
@username = opts.delete(:username)
|
11
|
+
setup_repositories(opts)
|
12
|
+
end
|
13
|
+
|
14
|
+
# Public: Add a repository (or array of repositories).
|
15
|
+
#
|
16
|
+
# repos - a 'username/repository' String or Array of such strings.
|
17
|
+
#
|
18
|
+
# Returns the updated array of repositories.
|
19
|
+
def add(repos)
|
20
|
+
@repositories.add(repos)
|
21
|
+
|
22
|
+
repositories
|
23
|
+
end
|
24
|
+
|
25
|
+
# Public: Return a user's OSS contributions as a hash. If the hash
|
26
|
+
# hasn't already been determined, the contributions are all looked
|
27
|
+
# up and stashed in an ivar: @contributions. If @contributions
|
28
|
+
# already exists, it is returned without the costly lookup being
|
29
|
+
# performed.
|
30
|
+
#
|
31
|
+
# Returns a Hash.
|
32
|
+
def contributions_as_hash
|
33
|
+
load_contributions unless @contributions
|
34
|
+
@contributions
|
35
|
+
end
|
36
|
+
|
37
|
+
# Public: Determine a user's contributions and load the
|
38
|
+
# @contributions ivar.
|
39
|
+
#
|
40
|
+
# Returns a Hash.
|
41
|
+
def load_contributions
|
42
|
+
@contributions = Hash.new
|
43
|
+
repositories.each do |f|
|
44
|
+
conts = get_contributions(f)
|
45
|
+
@contributions[f] = conts unless conts.empty?
|
46
|
+
end
|
47
|
+
|
48
|
+
@contributions
|
49
|
+
end
|
50
|
+
|
51
|
+
# Public: Replace the user's forked repositories with the specified
|
52
|
+
# repositories.
|
53
|
+
#
|
54
|
+
# repos - a 'username/repository_name' string, or an array of such
|
55
|
+
# strings.
|
56
|
+
#
|
57
|
+
# Returns the updated array of repositories.
|
58
|
+
def only(repos)
|
59
|
+
@repositories.only repos
|
60
|
+
|
61
|
+
repositories
|
62
|
+
end
|
63
|
+
|
64
|
+
# Public: Provide the names for all the forked projects.
|
65
|
+
#
|
66
|
+
# Example:
|
67
|
+
#
|
68
|
+
# user.repositories
|
69
|
+
# # => ['r/r', 's/s']
|
70
|
+
# user.project_names
|
71
|
+
# # => ['r', 's']
|
72
|
+
#
|
73
|
+
# Returns an Array.
|
74
|
+
def project_names
|
75
|
+
repositories.map { |s| s.match(/[^\/]*$/)[0] }
|
76
|
+
end
|
77
|
+
|
78
|
+
# Public: Remove a repository (or array of repositories).
|
79
|
+
#
|
80
|
+
# repos - a 'username/repository' String or Array of such strings.
|
81
|
+
#
|
82
|
+
# Returns the updated array of repositories.
|
83
|
+
def remove(repos)
|
84
|
+
@repositories.remove(repos)
|
85
|
+
|
86
|
+
repositories
|
87
|
+
end
|
88
|
+
|
89
|
+
# Public: Accessor method for the @repositories ivar.
|
90
|
+
#
|
91
|
+
# array - an array of 'username/repository_name' strings.
|
92
|
+
#
|
93
|
+
# Returns nothing.
|
94
|
+
def repositories=(array)
|
95
|
+
@repositories = RepositoryList.new(array)
|
96
|
+
end
|
97
|
+
|
98
|
+
# Public: Accessor method for the @repositories ivar.
|
99
|
+
#
|
100
|
+
# Returns an Array of 'username/repository_name' strings.
|
101
|
+
def repositories
|
102
|
+
@repositories.list
|
103
|
+
end
|
104
|
+
|
105
|
+
# Internal: attr_accessor for @contributions. This method really only
|
106
|
+
# exists for testing.
|
107
|
+
#
|
108
|
+
# hash - a hash.
|
109
|
+
#
|
110
|
+
# Returns a Hash.
|
111
|
+
def contributions=(hash)
|
112
|
+
@contributions = hash
|
113
|
+
end
|
114
|
+
|
115
|
+
# Internal: attr_reader for @contributions. This method really only
|
116
|
+
# exists for testing.
|
117
|
+
#
|
118
|
+
# Returns a Hash.
|
119
|
+
def contributions
|
120
|
+
@contributions
|
121
|
+
end
|
122
|
+
|
123
|
+
# Internal: Generate an array of forked repositories for the user.
|
124
|
+
# This array is set as the @repositories variable.
|
125
|
+
#
|
126
|
+
# opts - an array with, possible, keys for :only, :remove, and :add.
|
127
|
+
#
|
128
|
+
# Returns an Array of repositories.
|
129
|
+
def setup_repositories(opts)
|
130
|
+
@repositories = RepositoryList.new(GithubAPI.forks(@username))
|
131
|
+
update(opts)
|
132
|
+
end
|
133
|
+
|
134
|
+
# Internal: Get the user's contributions to the repository.
|
135
|
+
#
|
136
|
+
# Returns a Hash.
|
137
|
+
def get_contributions(repository)
|
138
|
+
Git.contributions GithubAPI.name(@username), repository
|
139
|
+
end
|
140
|
+
|
141
|
+
# Internal: Combine the user's explicit preferences with an array of
|
142
|
+
# forks.
|
143
|
+
#
|
144
|
+
# Returns an Array.
|
145
|
+
def update(opts)
|
146
|
+
opts.each_pair do |k,v|
|
147
|
+
@repositories.send(k.to_sym, v)
|
148
|
+
end
|
149
|
+
|
150
|
+
repositories
|
151
|
+
end
|
152
|
+
end
|
153
|
+
end
|
@@ -0,0 +1,66 @@
|
|
1
|
+
require 'tmpdir'
|
2
|
+
require 'fileutils'
|
3
|
+
require 'contributions/string_utils'
|
4
|
+
|
5
|
+
module Contributions
|
6
|
+
class Git
|
7
|
+
|
8
|
+
ENDING = " CONTRIBUTIONS_ENDING "
|
9
|
+
KEYS = [:sha, :date, :subject, :body]
|
10
|
+
SEPARATOR = " CONTRIBUTIONS_SEPARATOR "
|
11
|
+
|
12
|
+
# Public: Get all the contributions in a repository by a user
|
13
|
+
# (contributions for which the user is the *author*).
|
14
|
+
#
|
15
|
+
# user - a user's name (the name that shows up as the committer or
|
16
|
+
# author---e.g., 'John Smith'
|
17
|
+
# repository - a 'username/repository_name' string.
|
18
|
+
#
|
19
|
+
# Returns an Array of Hashes with keys for :sha, :date, :subject, :body
|
20
|
+
def self.contributions(user, repository)
|
21
|
+
log = self.clone(repository) { self.read_log(user) }
|
22
|
+
StringUtils.string_to_hash(log, KEYS, SEPARATOR, ENDING)
|
23
|
+
end
|
24
|
+
|
25
|
+
# Public: Clone a repository and run the block passed inside the
|
26
|
+
# newly cloned repository.
|
27
|
+
#
|
28
|
+
# repository - a 'username/repository_name' string.
|
29
|
+
# block - a block to be executed inside the newly cloned
|
30
|
+
# directory.
|
31
|
+
#
|
32
|
+
# Returns the return value of the block.
|
33
|
+
def self.clone(repository, &block)
|
34
|
+
value = ''
|
35
|
+
repo_name = repository.match(/([^\/]*$)/)[1]
|
36
|
+
Dir.mktmpdir(repo_name) do |dir|
|
37
|
+
cloned_repo_name = dir + '/' + repo_name
|
38
|
+
system "git clone -q https://github.com/#{repository} #{cloned_repo_name}"
|
39
|
+
Dir.chdir(cloned_repo_name) do
|
40
|
+
value = yield
|
41
|
+
end
|
42
|
+
|
43
|
+
FileUtils.rm_rf(cloned_repo_name)
|
44
|
+
end
|
45
|
+
|
46
|
+
value
|
47
|
+
end
|
48
|
+
|
49
|
+
# Internal: The command to read the git log.
|
50
|
+
#
|
51
|
+
# user - the user's name.
|
52
|
+
#
|
53
|
+
# Returns nothing.
|
54
|
+
def self.read_log(user)
|
55
|
+
# We want a string returned (for parsing); so use read over
|
56
|
+
# readlines.
|
57
|
+
IO.popen("git log --author='#{user}' --format='#{self.log_format}' --no-color") { |io| io.read }
|
58
|
+
end
|
59
|
+
|
60
|
+
def self.log_format
|
61
|
+
["%h", "%ci", "%s", "%b"].join(SEPARATOR) << ENDING
|
62
|
+
end
|
63
|
+
|
64
|
+
|
65
|
+
end
|
66
|
+
end
|
@@ -0,0 +1,62 @@
|
|
1
|
+
require 'open-uri'
|
2
|
+
require 'json'
|
3
|
+
|
4
|
+
module Contributions
|
5
|
+
class GithubAPI
|
6
|
+
|
7
|
+
# Public: Get just the user's repositories that are forks.
|
8
|
+
#
|
9
|
+
# Returns an Array.
|
10
|
+
def self.forks(username)
|
11
|
+
forks = self.repos(username).select { |r| r["fork"] == true }
|
12
|
+
repo_names = forks.map { |r| r["owner"]["login"] + '/' + r["name"] }
|
13
|
+
repo_names.map { |r| self.parent(r) }
|
14
|
+
end
|
15
|
+
|
16
|
+
# Public: Get the name of the user.
|
17
|
+
#
|
18
|
+
# username - github username.
|
19
|
+
#
|
20
|
+
# Returns a String.
|
21
|
+
def self.name(username)
|
22
|
+
self.user(username)["name"]
|
23
|
+
end
|
24
|
+
|
25
|
+
# Public: Get the name of the forked repository.
|
26
|
+
#
|
27
|
+
# repository - a 'username/repository_name' string.
|
28
|
+
#
|
29
|
+
# Returns a String.
|
30
|
+
def self.parent(repository)
|
31
|
+
username, repo_name = repository.split('/')
|
32
|
+
repo_info = self.repository(repository)
|
33
|
+
repo_info["parent"]["owner"]["login"] + '/' + repo_name
|
34
|
+
end
|
35
|
+
|
36
|
+
# Public: Get all the user's repositories.
|
37
|
+
#
|
38
|
+
# Returns an Array.
|
39
|
+
def self.repos(username)
|
40
|
+
JSON.parse(open("https://api.github.com/users/#{username}/repos?per_page=100") { |f| f.read } )
|
41
|
+
end
|
42
|
+
|
43
|
+
# Internal: Get the user info (all of it) from github.
|
44
|
+
#
|
45
|
+
# username - github username.
|
46
|
+
#
|
47
|
+
# Returns a Hash.
|
48
|
+
def self.user(username)
|
49
|
+
JSON.parse(open("https://api.github.com/users/#{username}") { |f| f.read } )
|
50
|
+
end
|
51
|
+
|
52
|
+
# Internal: Get the repository info (all of it) from github.
|
53
|
+
#
|
54
|
+
# repository - a 'username/repository_name' string.
|
55
|
+
#
|
56
|
+
# Returns a Hash.
|
57
|
+
def self.repository(repository)
|
58
|
+
JSON.parse(open("https://api.github.com/repos/#{repository}") { |f| f.read } )
|
59
|
+
end
|
60
|
+
|
61
|
+
end
|
62
|
+
end
|
@@ -0,0 +1,56 @@
|
|
1
|
+
module Contributions
|
2
|
+
class RepositoryList
|
3
|
+
|
4
|
+
attr_reader :list
|
5
|
+
|
6
|
+
def initialize(*args)
|
7
|
+
@list = [args].flatten
|
8
|
+
end
|
9
|
+
|
10
|
+
# Public: Add a string or array of strings to the repository list.
|
11
|
+
#
|
12
|
+
# repos - a string or an array of strings (each of which is a
|
13
|
+
# 'username/repo')
|
14
|
+
#
|
15
|
+
# Returns a RepositoryList
|
16
|
+
def add(repos)
|
17
|
+
@list.push(repos).flatten!
|
18
|
+
self
|
19
|
+
end
|
20
|
+
|
21
|
+
# Public: Turn repositories into key value pairs.
|
22
|
+
#
|
23
|
+
# Returns an Array of Hashes {:username, :repository}
|
24
|
+
def key_value_pairs
|
25
|
+
results = []
|
26
|
+
@list.each do |e|
|
27
|
+
p = e.split('/')
|
28
|
+
results.push Hash[:username => p[0], :repository => p[1]]
|
29
|
+
end
|
30
|
+
|
31
|
+
results
|
32
|
+
end
|
33
|
+
|
34
|
+
# Public: Replace list of repositories with the list provided.
|
35
|
+
#
|
36
|
+
# repos - a string or an array of strings (each of which is a
|
37
|
+
# 'username/repo')
|
38
|
+
#
|
39
|
+
# Returns a RepositoryList
|
40
|
+
def only(repos)
|
41
|
+
@list = [repos].flatten
|
42
|
+
self
|
43
|
+
end
|
44
|
+
|
45
|
+
# Public: Remove a string or array of strings from the repository
|
46
|
+
# list.
|
47
|
+
# repos - a string or an array of strings (each of which is a
|
48
|
+
# 'username/repo')
|
49
|
+
#
|
50
|
+
# Returns a RepositoryList
|
51
|
+
def remove(repos)
|
52
|
+
@list.delete_if { |e| [repos].flatten.include? e }
|
53
|
+
self
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
@@ -0,0 +1,133 @@
|
|
1
|
+
module Contributions
|
2
|
+
class StringUtils
|
3
|
+
|
4
|
+
# Public: Read a long string of commit data and turn it into a
|
5
|
+
# hash.
|
6
|
+
#
|
7
|
+
# string - a string of commit data.
|
8
|
+
# separator - the separator between values in an entry.
|
9
|
+
# ending - the separator between entries.
|
10
|
+
# keys - an Array of keys for the final hash.
|
11
|
+
#
|
12
|
+
# Returns a Hash.
|
13
|
+
def self.string_to_hash(string, keys, separator, *ending)
|
14
|
+
s = string.dup
|
15
|
+
s_as_array = ending.empty? ? [s] : self.split!(s, ending[0])
|
16
|
+
|
17
|
+
s_as_array.map! { |e| self.split!(e, separator) }
|
18
|
+
self.remove_empty(s_as_array)
|
19
|
+
|
20
|
+
s_as_array.map! do |e|
|
21
|
+
e.map! { |line| line.strip }
|
22
|
+
self.zip_to_hash(keys, e)
|
23
|
+
end
|
24
|
+
|
25
|
+
s_as_array.map do |e|
|
26
|
+
self.short_dates(e)
|
27
|
+
end
|
28
|
+
|
29
|
+
# s_as_array.map do |e|
|
30
|
+
# self.zip_to_hash(keys, e)
|
31
|
+
# end
|
32
|
+
|
33
|
+
|
34
|
+
# self.split!(s, ending).each do |e|
|
35
|
+
# self.remove_empty(self.split!(e, separator))
|
36
|
+
# end
|
37
|
+
|
38
|
+
# Now we need the zip move, then to a hash, then replace nils with
|
39
|
+
# ''
|
40
|
+
|
41
|
+
# s = string.dup
|
42
|
+
# array = string.split()
|
43
|
+
# small_arrays = array.map { |e| e.split(separator).map { |l| l.strip } }
|
44
|
+
# small_arrays.delete_if { |a| a[0] == "" and a[1] == nil }
|
45
|
+
# small_arrays.map! { |a| [:sha, :date, :subject, :body].zip a }
|
46
|
+
# small_arrays
|
47
|
+
|
48
|
+
|
49
|
+
|
50
|
+
|
51
|
+
# results = []
|
52
|
+
# s = string.dup
|
53
|
+
# s.gsub!(/(\d{4}-\d{2}-\d{2})/) { |f| " BREAK " + f }
|
54
|
+
# s = s.split(" BREAK ")
|
55
|
+
# s.delete_if { |l| l.empty? }
|
56
|
+
# s.each do |line|
|
57
|
+
# m = /(?<date>\d{4}-\d{2}-\d{2})/.match line
|
58
|
+
# results.push m["date"]
|
59
|
+
# end
|
60
|
+
|
61
|
+
# results
|
62
|
+
end
|
63
|
+
|
64
|
+
# Public: Split the string on the give separator.
|
65
|
+
#
|
66
|
+
# separator - the character(s) on which to split the string.
|
67
|
+
#
|
68
|
+
# Returns a modified version of the string.
|
69
|
+
def self.split!(string, separator)
|
70
|
+
string.split(separator)
|
71
|
+
end
|
72
|
+
|
73
|
+
# Internal: Remove any empty arrays after a split.
|
74
|
+
#
|
75
|
+
# array - an array of Strings.
|
76
|
+
#
|
77
|
+
# Returns an Array of Strings (modified)
|
78
|
+
def self.remove_empty(array)
|
79
|
+
array.delete_if { |a| self.practically_empty?(a[0]) && a[1].nil? }
|
80
|
+
end
|
81
|
+
|
82
|
+
# Internal: Determine whether a string has any content.
|
83
|
+
#
|
84
|
+
# Examples:
|
85
|
+
#
|
86
|
+
# StringUtils.practically_empty?('')
|
87
|
+
# # => true
|
88
|
+
# StringUtils.practically_empty?("\n\n")
|
89
|
+
# # => true
|
90
|
+
# StringUtils.practically_empty?("a\n")
|
91
|
+
# # => false
|
92
|
+
#
|
93
|
+
# Returns a Boolean.
|
94
|
+
def self.practically_empty?(arg)
|
95
|
+
if arg.empty?
|
96
|
+
return true
|
97
|
+
elsif !arg.match /\w/
|
98
|
+
return true
|
99
|
+
else
|
100
|
+
return false
|
101
|
+
end
|
102
|
+
end
|
103
|
+
|
104
|
+
# Internal: Convert a pair of arrays into a hash with the first as
|
105
|
+
# keys.
|
106
|
+
#
|
107
|
+
# keys - an Array of keys.
|
108
|
+
# values - an Array of values.
|
109
|
+
#
|
110
|
+
# Returns a Hash.
|
111
|
+
def self.zip_to_hash(keys, values)
|
112
|
+
value = Hash.new
|
113
|
+
zipped = keys.zip values
|
114
|
+
zipped.each do |pair|
|
115
|
+
value[pair.first] = pair.last || ''
|
116
|
+
end
|
117
|
+
|
118
|
+
value
|
119
|
+
end
|
120
|
+
|
121
|
+
# Internal: Convert date format to a simpler one.
|
122
|
+
#
|
123
|
+
# hash - a hash with a :date key
|
124
|
+
#
|
125
|
+
# Returns a Hash.
|
126
|
+
def self.short_dates(hash)
|
127
|
+
old_date = hash[:date]
|
128
|
+
hash[:date] = old_date.match(/(\d{4}-\d{2}-\d{2})/)[1]
|
129
|
+
|
130
|
+
hash
|
131
|
+
end
|
132
|
+
end
|
133
|
+
end
|
@@ -0,0 +1,78 @@
|
|
1
|
+
require 'teststrap'
|
2
|
+
require 'contributions'
|
3
|
+
|
4
|
+
context "Contributions::Contributions" do
|
5
|
+
hookup { RR.reset }
|
6
|
+
helper(:full) { Contributions::Contributions.new(:username => 'charlietanksley') }
|
7
|
+
|
8
|
+
context ".new finds out about all forks" do
|
9
|
+
setup { full }
|
10
|
+
asserts(:repositories).includes "msanders/snipmate.vim"
|
11
|
+
asserts(:repositories).includes "thumblemonks/riot"
|
12
|
+
end
|
13
|
+
|
14
|
+
context ".new with an :only modifies the forks" do
|
15
|
+
setup { Contributions::Contributions.new(:username => 'charlietanksley', :only => 'thumblemonks/riot') }
|
16
|
+
asserts(:repositories).equals ['thumblemonks/riot']
|
17
|
+
end
|
18
|
+
|
19
|
+
context ".new with a :remove subtracts a fork" do
|
20
|
+
setup { Contributions::Contributions.new(:username => 'charlietanksley', :remove => 'thumblemonks/riot') }
|
21
|
+
denies(:repositories).includes 'thumblemonks/riot'
|
22
|
+
asserts("we have subtracted 1") { full.repositories.count - topic.repositories.count == 1 }.equals true
|
23
|
+
end
|
24
|
+
|
25
|
+
context ".new will :remove with an array as an argument" do
|
26
|
+
setup { Contributions::Contributions.new(:username => 'charlietanksley', :remove => ['thumblemonks/riot', 'rubinius/rubinius']) }
|
27
|
+
denies(:repositories).includes 'thumblemonks/riot'
|
28
|
+
denies(:repositories).includes 'rubinius/rubinius'
|
29
|
+
end
|
30
|
+
|
31
|
+
context ".new with an :add adds a fork" do
|
32
|
+
setup { Contributions::Contributions.new(:username => 'charlietanksley', :add => 'thumblemonks/chicago') }
|
33
|
+
asserts(:repositories).includes 'thumblemonks/chicago'
|
34
|
+
asserts("we have added 1") { topic.repositories.count - full.repositories.count == 1 }.equals true
|
35
|
+
end
|
36
|
+
|
37
|
+
context ".new will :add with an array as an argument" do
|
38
|
+
setup { Contributions::Contributions.new(:username => 'charlietanksley', :add => ['t/r', 'r/r']) }
|
39
|
+
asserts(:repositories).includes 't/r'
|
40
|
+
asserts(:repositories).includes 'r/r'
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
context "a full run of Contributions::Contributions" do
|
45
|
+
setup { Contributions::Contributions.new(:username => 'charlietanksley',
|
46
|
+
:only => ['thumblemonks/riot', 'msanders/snipmate.vim', 'davetron5000/methadone']) }
|
47
|
+
|
48
|
+
context "starts with a list of repositories" do
|
49
|
+
asserts(:repositories).equals ['thumblemonks/riot', 'msanders/snipmate.vim', 'davetron5000/methadone']
|
50
|
+
end
|
51
|
+
|
52
|
+
context "does not start with any contributions" do
|
53
|
+
asserts(:contributions).nil
|
54
|
+
end
|
55
|
+
|
56
|
+
context "grabs the contributions when asked" do
|
57
|
+
|
58
|
+
context "with the right keys" do
|
59
|
+
setup { topic.contributions_as_hash.keys }
|
60
|
+
asserts_topic.includes 'thumblemonks/riot'
|
61
|
+
asserts_topic.includes 'davetron5000/methadone'
|
62
|
+
end
|
63
|
+
|
64
|
+
context "and saves them off" do
|
65
|
+
hookup { topic.contributions_as_hash }
|
66
|
+
setup { topic.contributions.keys }
|
67
|
+
asserts_topic.includes 'thumblemonks/riot'
|
68
|
+
asserts_topic.includes 'davetron5000/methadone'
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
context "when the list is updated" do
|
73
|
+
hookup { topic.remove ['thumblemonks/riot', 'davetron5000/methadone'] }
|
74
|
+
hookup { topic.load_contributions }
|
75
|
+
asserts { topic.contributions_as_hash.keys }.equals []
|
76
|
+
asserts(:contributions_as_hash).equals Hash[]
|
77
|
+
end
|
78
|
+
end
|
@@ -0,0 +1,112 @@
|
|
1
|
+
require 'teststrap'
|
2
|
+
require 'contributions'
|
3
|
+
|
4
|
+
context "Contributions public api with mocked .new" do
|
5
|
+
hookup do
|
6
|
+
stub(Contributions::GithubAPI).forks(anything) { ['r/r', 's/s'] }
|
7
|
+
end
|
8
|
+
|
9
|
+
setup { Contributions::Contributions.new(:username => 'charlietanksley') }
|
10
|
+
|
11
|
+
context ".new" do
|
12
|
+
context "assignments" do
|
13
|
+
asserts_topic.assigns :username
|
14
|
+
asserts_topic.assigns :repositories
|
15
|
+
end
|
16
|
+
|
17
|
+
asserts(:repositories).equals ['r/r', 's/s']
|
18
|
+
end
|
19
|
+
|
20
|
+
context ".add will add repositories" do
|
21
|
+
context "if given a string" do
|
22
|
+
setup { topic.add 'added/added' }
|
23
|
+
asserts_topic.includes 'added/added'
|
24
|
+
end
|
25
|
+
|
26
|
+
context "if given an array" do
|
27
|
+
setup { topic.add ['added/first', 'added/second'] }
|
28
|
+
asserts_topic.includes 'added/first'
|
29
|
+
asserts_topic.includes 'added/second'
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
context ".contributions_as_hash" do
|
34
|
+
context "does not reload hash if it already exists" do
|
35
|
+
hookup do
|
36
|
+
topic.contributions = Hash[:k => 'v']
|
37
|
+
dont_allow(topic).load_contributions
|
38
|
+
end
|
39
|
+
|
40
|
+
asserts(:contributions_as_hash).equals Hash[:k => 'v']
|
41
|
+
end
|
42
|
+
|
43
|
+
context "calls #load_contributions if there aren't any contributions yet" do
|
44
|
+
hookup { mock(topic).load_contributions { topic.contributions = Hash[:k => 'v'] } }
|
45
|
+
asserts(:contributions_as_hash).equals Hash[:k => 'v']
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
context ".only will trade out repositories" do
|
50
|
+
context "if given a string" do
|
51
|
+
setup { topic.only 'added/added' }
|
52
|
+
asserts_topic.includes 'added/added'
|
53
|
+
denies_topic.includes 'r/r'
|
54
|
+
denies_topic.includes 's/s'
|
55
|
+
end
|
56
|
+
|
57
|
+
context "if given an array" do
|
58
|
+
setup { topic.only ['added/first', 'added/second'] }
|
59
|
+
asserts_topic.includes 'added/first'
|
60
|
+
asserts_topic.includes 'added/second'
|
61
|
+
denies_topic.includes 'r/r'
|
62
|
+
denies_topic.includes 's/s'
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
context ".project_names returns an array of project names" do
|
67
|
+
asserts(:project_names).equals { ['r', 's'] }
|
68
|
+
end
|
69
|
+
|
70
|
+
context ".load_contributions" do
|
71
|
+
context "actually calls out to get the contributions for each fork" do
|
72
|
+
hookup do
|
73
|
+
mock(Contributions::Git).contributions('Charlie Tanksley', 'r/r') { Hash[:sha => 1] }
|
74
|
+
mock(Contributions::Git).contributions('Charlie Tanksley', 's/s') { Hash[:sha => 2] }
|
75
|
+
end
|
76
|
+
|
77
|
+
denies(:load_contributions).nil
|
78
|
+
end
|
79
|
+
|
80
|
+
context "returns a hash with repository names as keys" do
|
81
|
+
hookup do
|
82
|
+
stub(Contributions::Git).contributions('Charlie Tanksley', 'r/r') { Hash[:sha => 1] }
|
83
|
+
stub(Contributions::Git).contributions('Charlie Tanksley', 's/s') { Hash[:sha => 2] }
|
84
|
+
end
|
85
|
+
|
86
|
+
asserts(:load_contributions).equals Hash['r/r' => Hash[:sha => 1], 's/s' => Hash[:sha => 2]]
|
87
|
+
end
|
88
|
+
|
89
|
+
context "stashes the results in an instance variable" do
|
90
|
+
hookup do
|
91
|
+
stub(Contributions::Git).contributions('Charlie Tanksley', 'r/r') { Hash[:sha => 1] }
|
92
|
+
stub(Contributions::Git).contributions('Charlie Tanksley', 's/s') { Hash[:sha => 2] }
|
93
|
+
topic.load_contributions
|
94
|
+
end
|
95
|
+
|
96
|
+
asserts(:contributions).equals Hash['r/r' => Hash[:sha => 1], 's/s' => Hash[:sha => 2]]
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
context ".remove will drop repositories" do
|
101
|
+
context "if given a string" do
|
102
|
+
setup { topic.remove 'r/r' }
|
103
|
+
denies_topic.includes 'added/added'
|
104
|
+
end
|
105
|
+
|
106
|
+
context "if given an array" do
|
107
|
+
setup { topic.remove ['r/r', 's/s'] }
|
108
|
+
denies_topic.includes 'r/r'
|
109
|
+
denies_topic.includes 's/s'
|
110
|
+
end
|
111
|
+
end
|
112
|
+
end
|
data/test/git_test.rb
ADDED
@@ -0,0 +1,28 @@
|
|
1
|
+
require 'teststrap'
|
2
|
+
require 'contributions/git'
|
3
|
+
|
4
|
+
context "Contributions::Git" do
|
5
|
+
context ".contributions" do
|
6
|
+
context 'determines all the user\'s contributions' do
|
7
|
+
setup { Contributions::Git.contributions('Charlie Tanksley', 'thumblemonks/riot') }
|
8
|
+
|
9
|
+
asserts_topic.size 6
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
context ".clone creates a clone and runs the block you pass it" do
|
14
|
+
helper(:command) { "git log --author='Charlie Tanksley' --format='%h %ci %s %b' --no-color" }
|
15
|
+
setup do
|
16
|
+
Contributions::Git.clone('thumblemonks/riot') { IO.popen(command) { |io| io.readlines } }
|
17
|
+
end
|
18
|
+
|
19
|
+
asserts_topic.size 6
|
20
|
+
end
|
21
|
+
|
22
|
+
context ".log_format" do
|
23
|
+
setup { Contributions::Git.log_format }
|
24
|
+
|
25
|
+
asserts_topic.equals "%h CONTRIBUTIONS_SEPARATOR %ci CONTRIBUTIONS_SEPARATOR %s CONTRIBUTIONS_SEPARATOR %b CONTRIBUTIONS_ENDING "
|
26
|
+
end
|
27
|
+
|
28
|
+
end
|
@@ -0,0 +1,41 @@
|
|
1
|
+
require 'teststrap'
|
2
|
+
require 'contributions/github_api'
|
3
|
+
|
4
|
+
context "Contributions::GithubAPI" do
|
5
|
+
context ".forks" do
|
6
|
+
setup { Contributions::GithubAPI.forks('charlietanksley') }
|
7
|
+
|
8
|
+
context "gets a list of all the forks" do
|
9
|
+
setup { topic.count }
|
10
|
+
denies_topic.equals 0
|
11
|
+
end
|
12
|
+
|
13
|
+
context "gets the original names, not the name of the fork" do
|
14
|
+
denies_topic.includes 'charlietanksley/riot'
|
15
|
+
asserts_topic.includes 'thumblemonks/riot'
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
context ".repos gets all the repositories when there are less than 100" do
|
20
|
+
setup { Contributions::GithubAPI.repos('rubinius').count }
|
21
|
+
|
22
|
+
asserts_topic.equals JSON.parse(open("https://api.github.com/users/rubinius") { |f| f.read })["public_repos"]
|
23
|
+
end
|
24
|
+
|
25
|
+
# Pending
|
26
|
+
context ".repos gets all the repositories when there are tons" do
|
27
|
+
# setup { Contributions::GithubAPI.repos('vim-scripts').count }
|
28
|
+
|
29
|
+
# asserts_topic.equals JSON.parse(open("https://api.github.com/users/vim-scripts") { |f| f.read })["public_repos"]
|
30
|
+
end
|
31
|
+
|
32
|
+
context ".name returns the name of the user" do
|
33
|
+
setup { Contributions::GithubAPI.name('charlietanksley') }
|
34
|
+
asserts_topic.equals 'Charlie Tanksley'
|
35
|
+
end
|
36
|
+
|
37
|
+
context ".parent returns the name of the forked repository" do
|
38
|
+
setup { Contributions::GithubAPI.parent('charlietanksley/riot') }
|
39
|
+
asserts_topic.equals 'thumblemonks/riot'
|
40
|
+
end
|
41
|
+
end
|
@@ -0,0 +1,73 @@
|
|
1
|
+
require 'teststrap'
|
2
|
+
require 'contributions/repository_list'
|
3
|
+
|
4
|
+
context "RepositoryList" do
|
5
|
+
context "#add" do
|
6
|
+
context "with a blank canvas" do
|
7
|
+
setup { Contributions::RepositoryList.new }
|
8
|
+
|
9
|
+
context "adds a single string" do
|
10
|
+
hookup { topic.add('rubinius/rubinius') }
|
11
|
+
asserts(:list).equals ['rubinius/rubinius']
|
12
|
+
end
|
13
|
+
|
14
|
+
context "adds an array of strings" do
|
15
|
+
hookup { topic.add ['rubinius/rubinius', 'vim-scripts/test.vim'] }
|
16
|
+
asserts(:list).equals ['rubinius/rubinius', 'vim-scripts/test.vim']
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
context "with a starting array" do
|
21
|
+
setup { Contributions::RepositoryList.new(['s/s']) }
|
22
|
+
|
23
|
+
context "adds a single string" do
|
24
|
+
hookup { topic.add 'rubinius/rubinius' }
|
25
|
+
asserts(:list).equals ['s/s', 'rubinius/rubinius']
|
26
|
+
end
|
27
|
+
|
28
|
+
context "adds an array of strings" do
|
29
|
+
hookup { topic.add ['rubinius/rubinius', 'vim-scripts/test.vim'] }
|
30
|
+
asserts(:list).equals ['s/s', 'rubinius/rubinius', 'vim-scripts/test.vim']
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
context "#remove" do
|
36
|
+
setup { Contributions::RepositoryList.new(['s/s', 's/ss', 'o/other']) }
|
37
|
+
|
38
|
+
context "selectively removes a single repo" do
|
39
|
+
hookup { topic.remove 's/s' }
|
40
|
+
asserts(:list).equals ['s/ss', 'o/other']
|
41
|
+
end
|
42
|
+
|
43
|
+
context "selectively removes an array of repos" do
|
44
|
+
hookup { topic.remove ['s/s', 's/ss'] }
|
45
|
+
asserts(:list).equals ['o/other']
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
context "#only completely replaces the list" do
|
50
|
+
setup { Contributions::RepositoryList.new(['s/s', 's/ss', 'o/other']) }
|
51
|
+
|
52
|
+
context "when given a single repo" do
|
53
|
+
hookup { topic.only 'a/a' }
|
54
|
+
asserts(:list).equals ['a/a']
|
55
|
+
end
|
56
|
+
|
57
|
+
context "when given an array" do
|
58
|
+
hookup { topic.only ['a/a', 'b/b'] }
|
59
|
+
asserts(:list).equals ['a/a', 'b/b']
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
context "#key_value_pairs splits into key value pairs" do
|
64
|
+
setup { Contributions::RepositoryList.new(['s/s', 's/ss', 'o/other']) }
|
65
|
+
asserts(:key_value_pairs).equals [{:username => 's', :repository => 's'},
|
66
|
+
{:username => 's', :repository => 'ss'},
|
67
|
+
{:username => 'o', :repository => 'other'}]
|
68
|
+
end
|
69
|
+
|
70
|
+
end
|
71
|
+
|
72
|
+
|
73
|
+
|
@@ -0,0 +1,62 @@
|
|
1
|
+
require 'teststrap'
|
2
|
+
require 'contributions/string_utils'
|
3
|
+
|
4
|
+
context "StringUtils" do
|
5
|
+
helper(:string) { "1b90c5e CONTRIBUTIONS_SEPARATOR 2011-09-13 07:49:43 -0500 CONTRIBUTIONS_SEPARATOR remove mention of deprecated exists from README CONTRIBUTIONS_SEPARATOR CONTRIBUTIONS_ENDING \n1620141 CONTRIBUTIONS_SEPARATOR 2011-12-16 19:26:58 -0500 CONTRIBUTIONS_SEPARATOR adjust input boxes to be same height as buttons CONTRIBUTIONS_SEPARATOR There was this weird thing happening where the inline submit button on a\nform would be like twice as tall as the text input area (notably on the\nuser_search form, but in a few other places it looked weird). This\nmakes all those a standard height (the same as the default Twitter\nbutton height). CONTRIBUTIONS_ENDING \n\n" }
|
6
|
+
helper(:ending) { " CONTRIBUTIONS_ENDING " }
|
7
|
+
helper(:separator) { " CONTRIBUTIONS_SEPARATOR " }
|
8
|
+
|
9
|
+
context ".split!" do
|
10
|
+
context "splits the string in place" do
|
11
|
+
setup { Contributions::StringUtils.split!(string, ending) }
|
12
|
+
|
13
|
+
asserts_topic.size 3
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
context ".remove_empty throws out any 'blank' arrays" do
|
18
|
+
setup { Contributions::StringUtils.remove_empty([['s'], ['', 's'], [''], ["\n\n"]]) }
|
19
|
+
asserts_topic.size 2
|
20
|
+
end
|
21
|
+
|
22
|
+
context ".zip_to_hash" do
|
23
|
+
context "creates a hash with the first array as the key" do
|
24
|
+
setup { Contributions::StringUtils.zip_to_hash([:k, :v, :e], ['key', 'value', 'empty']) }
|
25
|
+
asserts_topic.equals Hash[:k => 'key', :v => 'value', :e => 'empty']
|
26
|
+
end
|
27
|
+
|
28
|
+
context "creates a hash with the first array as the key and '' for any empty values" do
|
29
|
+
setup { Contributions::StringUtils.zip_to_hash([:k, :v, :e], ['key']) }
|
30
|
+
asserts_topic.equals Hash[:k => 'key', :v => '', :e => '']
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
context ".string_to_hash turns a hash of commit info into a useful hash" do
|
35
|
+
setup do
|
36
|
+
one = "1b90c5e CONTRIBUTIONS_SEPARATOR 2011-09-13 07:49:43 -0500 CONTRIBUTIONS_SEPARATOR remove mention of deprecated exists from README CONTRIBUTIONS_SEPARATOR CONTRIBUTIONS_ENDING \n"
|
37
|
+
two = "7201941 CONTRIBUTIONS_SEPARATOR 2011-09-13 07:48:53 -0500 CONTRIBUTIONS_SEPARATOR add deprecation warning to exists macro CONTRIBUTIONS_SEPARATOR CONTRIBUTIONS_ENDING \n"
|
38
|
+
three = "1620141 CONTRIBUTIONS_SEPARATOR 2011-12-16 19:26:58 -0500 CONTRIBUTIONS_SEPARATOR adjust input boxes to be same height as buttons CONTRIBUTIONS_SEPARATOR There was this weird thing happening where the inline submit button on a\nform would be like twice as tall as the text input area (notably on the\nuser_search form, but in a few other places it looked weird). This\nmakes all those a standard height (the same as the default Twitter\nbutton height). CONTRIBUTIONS_ENDING \n\n"
|
39
|
+
Contributions::StringUtils.string_to_hash([one, two, three].join, [:sha, :date, :subject, :body], separator, ending)
|
40
|
+
end
|
41
|
+
|
42
|
+
asserts_topic.equals [{:sha => "1b90c5e",
|
43
|
+
:date => "2011-09-13",
|
44
|
+
:subject => "remove mention of deprecated exists from README",
|
45
|
+
:body => ''},
|
46
|
+
{:sha=>"7201941",
|
47
|
+
:date=>"2011-09-13",
|
48
|
+
:subject=>"add deprecation warning to exists macro",
|
49
|
+
:body=>""},
|
50
|
+
{:sha=>"1620141",
|
51
|
+
:date=>"2011-12-16",
|
52
|
+
:subject=>"adjust input boxes to be same height as buttons",
|
53
|
+
:body=>"There was this weird thing happening where the inline submit button on a\nform would be like twice as tall as the text input area (notably on the\nuser_search form, but in a few other places it looked weird). This\nmakes all those a standard height (the same as the default Twitter\nbutton height)."}]
|
54
|
+
end
|
55
|
+
|
56
|
+
context ".short_dates gives you cleaner dates" do
|
57
|
+
setup { Contributions::StringUtils.short_dates(Hash[:sha=>"7201941", :date=>"2011-09-13 07:48:53 -0500", :subject=>"add deprecation warning to exists macro", :body=>""]) }
|
58
|
+
|
59
|
+
asserts_topic.equals Hash[:sha=>"7201941", :date=>"2011-09-13", :subject=>"add deprecation warning to exists macro", :body=>""]
|
60
|
+
end
|
61
|
+
|
62
|
+
end
|
data/test/teststrap.rb
ADDED
metadata
ADDED
@@ -0,0 +1,108 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: contributions
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.1
|
5
|
+
prerelease:
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Charlie Tanksley
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2012-03-01 00:00:00.000000000 Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: json
|
16
|
+
requirement: &70340630508160 !ruby/object:Gem::Requirement
|
17
|
+
none: false
|
18
|
+
requirements:
|
19
|
+
- - ! '>='
|
20
|
+
- !ruby/object:Gem::Version
|
21
|
+
version: '0'
|
22
|
+
type: :runtime
|
23
|
+
prerelease: false
|
24
|
+
version_requirements: *70340630508160
|
25
|
+
- !ruby/object:Gem::Dependency
|
26
|
+
name: riot
|
27
|
+
requirement: &70340630505820 !ruby/object:Gem::Requirement
|
28
|
+
none: false
|
29
|
+
requirements:
|
30
|
+
- - ! '>='
|
31
|
+
- !ruby/object:Gem::Version
|
32
|
+
version: '0'
|
33
|
+
type: :development
|
34
|
+
prerelease: false
|
35
|
+
version_requirements: *70340630505820
|
36
|
+
- !ruby/object:Gem::Dependency
|
37
|
+
name: rake
|
38
|
+
requirement: &70340630514680 !ruby/object:Gem::Requirement
|
39
|
+
none: false
|
40
|
+
requirements:
|
41
|
+
- - ! '>='
|
42
|
+
- !ruby/object:Gem::Version
|
43
|
+
version: '0'
|
44
|
+
type: :development
|
45
|
+
prerelease: false
|
46
|
+
version_requirements: *70340630514680
|
47
|
+
description: Gather your contributions to OSS projects (hosted on github!).
|
48
|
+
email:
|
49
|
+
- charlie.tanksley@gmail.com
|
50
|
+
executables: []
|
51
|
+
extensions: []
|
52
|
+
extra_rdoc_files: []
|
53
|
+
files:
|
54
|
+
- .gitignore
|
55
|
+
- .travis.yml
|
56
|
+
- Gemfile
|
57
|
+
- LICENSE
|
58
|
+
- README.md
|
59
|
+
- Rakefile
|
60
|
+
- TODO.markdown
|
61
|
+
- contributions.gemspec
|
62
|
+
- lib/contributions.rb
|
63
|
+
- lib/contributions/contributions.rb
|
64
|
+
- lib/contributions/git.rb
|
65
|
+
- lib/contributions/github_api.rb
|
66
|
+
- lib/contributions/repository_list.rb
|
67
|
+
- lib/contributions/string_utils.rb
|
68
|
+
- lib/contributions/version.rb
|
69
|
+
- test/contributions_integration_test.rb
|
70
|
+
- test/contributions_test.rb
|
71
|
+
- test/git_test.rb
|
72
|
+
- test/github_api_test.rb
|
73
|
+
- test/repository_list_test.rb
|
74
|
+
- test/string_utils_test.rb
|
75
|
+
- test/teststrap.rb
|
76
|
+
homepage: ''
|
77
|
+
licenses: []
|
78
|
+
post_install_message:
|
79
|
+
rdoc_options: []
|
80
|
+
require_paths:
|
81
|
+
- lib
|
82
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
83
|
+
none: false
|
84
|
+
requirements:
|
85
|
+
- - ! '>='
|
86
|
+
- !ruby/object:Gem::Version
|
87
|
+
version: '0'
|
88
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
89
|
+
none: false
|
90
|
+
requirements:
|
91
|
+
- - ! '>='
|
92
|
+
- !ruby/object:Gem::Version
|
93
|
+
version: '0'
|
94
|
+
requirements: []
|
95
|
+
rubyforge_project:
|
96
|
+
rubygems_version: 1.8.11
|
97
|
+
signing_key:
|
98
|
+
specification_version: 3
|
99
|
+
summary: A gem to find all your contributions to OSS projects on github and make that
|
100
|
+
information easy to access.
|
101
|
+
test_files:
|
102
|
+
- test/contributions_integration_test.rb
|
103
|
+
- test/contributions_test.rb
|
104
|
+
- test/git_test.rb
|
105
|
+
- test/github_api_test.rb
|
106
|
+
- test/repository_list_test.rb
|
107
|
+
- test/string_utils_test.rb
|
108
|
+
- test/teststrap.rb
|