contributors_stats 1.0.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.
Files changed (63) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +6 -0
  3. data/.travis.yml +11 -0
  4. data/Gemfile +5 -0
  5. data/LICENSE.md +14 -0
  6. data/README.md +43 -0
  7. data/Rakefile +28 -0
  8. data/contributors_stats.gemspec +24 -0
  9. data/lib/contributors_stats.rb +28 -0
  10. data/lib/contributors_stats/base.rb +29 -0
  11. data/lib/contributors_stats/calculator.rb +50 -0
  12. data/lib/contributors_stats/formatter.rb +57 -0
  13. data/lib/contributors_stats/json_helper.rb +49 -0
  14. data/lib/contributors_stats/reader.rb +52 -0
  15. data/lib/contributors_stats/version.rb +4 -0
  16. data/lib/example_data.rb +58 -0
  17. data/lib/plugins/contributors_stats/formatter/html.rb +26 -0
  18. data/lib/plugins/contributors_stats/reader/gh_org.rb +21 -0
  19. data/lib/plugins/contributors_stats/reader/gh_repo.rb +19 -0
  20. data/lib/plugins/contributors_stats/updater/html.rb +36 -0
  21. data/lib/plugins/contributors_stats/user_data/fetch.rb +16 -0
  22. data/lib/plugins/contributors_stats/user_data/simple.rb +23 -0
  23. data/test/contributors_stats/base_test.rb +36 -0
  24. data/test/contributors_stats/calculator_test.rb +70 -0
  25. data/test/contributors_stats/formatter_test.rb +77 -0
  26. data/test/contributors_stats/json_helper_test.rb +76 -0
  27. data/test/contributors_stats/reader_test.rb +59 -0
  28. data/test/contributors_stats_test.rb +13 -0
  29. data/test/fixtures-gh/orgs/railsinstaller/repos.json +269 -0
  30. data/test/fixtures-gh/repos/mpapis/rubygems-bundler/contributors.json +173 -0
  31. data/test/fixtures-gh/repos/railsinstaller/railsinstaller-nix/contributors.json +40 -0
  32. data/test/fixtures-gh/repos/railsinstaller/railsinstaller-windows/contributors.json +116 -0
  33. data/test/fixtures-gh/repos/railsinstaller/website/contributors.json +192 -0
  34. data/test/fixtures-gh/repos/rvm/pluginator/contributors.json +40 -0
  35. data/test/fixtures-gh/users/acco.json +31 -0
  36. data/test/fixtures-gh/users/alexch.json +31 -0
  37. data/test/fixtures-gh/users/biow0lf.json +31 -0
  38. data/test/fixtures-gh/users/drnic.json +31 -0
  39. data/test/fixtures-gh/users/ebertech.json +31 -0
  40. data/test/fixtures-gh/users/edwardchiu38.json +31 -0
  41. data/test/fixtures-gh/users/emachnic.json +31 -0
  42. data/test/fixtures-gh/users/envygeeks.json +31 -0
  43. data/test/fixtures-gh/users/gpxl.json +31 -0
  44. data/test/fixtures-gh/users/jc00ke.json +31 -0
  45. data/test/fixtures-gh/users/joshbuddy.json +31 -0
  46. data/test/fixtures-gh/users/luigidr.json +31 -0
  47. data/test/fixtures-gh/users/luislavena.json +31 -0
  48. data/test/fixtures-gh/users/metaskills.json +31 -0
  49. data/test/fixtures-gh/users/mpapis.json +31 -0
  50. data/test/fixtures-gh/users/parndt.json +31 -0
  51. data/test/fixtures-gh/users/pw.json +31 -0
  52. data/test/fixtures-gh/users/tjouan.json +31 -0
  53. data/test/fixtures-gh/users/veganstraightedge.json +31 -0
  54. data/test/fixtures-gh/users/wayneeseguin.json +31 -0
  55. data/test/fixtures-gh/users/whitequark.json +31 -0
  56. data/test/plugins/formatter/html_test.rb +28 -0
  57. data/test/plugins/reader/gh_org_test.rb +32 -0
  58. data/test/plugins/reader/gh_repo_test.rb +31 -0
  59. data/test/plugins/updater/html_test.rb +55 -0
  60. data/test/plugins/user_data/fetch_test.rb +16 -0
  61. data/test/plugins/user_data/simple_test.rb +18 -0
  62. data/test/test_helper.rb +27 -0
  63. metadata +229 -0
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 8c726ad0986d83954b3b2bddde135824e1421dc2
4
+ data.tar.gz: 676fca44931563548b82c5abeb09cf34ae94de8b
5
+ SHA512:
6
+ metadata.gz: 83969f2f0da917ba5d1a5a0ce2c43785e8a7e90073a7b140c98493252fb97228c68017330cdd1bccd3f0d1427cfdfd1ba0bb715793c5d0123262290c2895c39a
7
+ data.tar.gz: acd9a3de698b3903ab65f6950e3c6bb930fcf1f2f6ca736cd806b0245e614d23b88503bbfe4695aad04882018b0ecab61063d97a511649095f832949f9541deb
data/.gitignore ADDED
@@ -0,0 +1,6 @@
1
+ /coverage
2
+ /doc
3
+ /.yardoc
4
+ /vendor
5
+ /*.gem
6
+ /Gemfile.lock
data/.travis.yml ADDED
@@ -0,0 +1,11 @@
1
+ language: ruby
2
+ rvm:
3
+ - 2.0.0
4
+ notifications:
5
+ irc:
6
+ channels:
7
+ - "irc.freenode.org#rvm-test"
8
+ email:
9
+ recipients:
10
+ - mpapis@gmail.com
11
+ on_failure: change
data/Gemfile ADDED
@@ -0,0 +1,5 @@
1
+ source "https://rubygems.org"
2
+
3
+ #ruby=2.0.0
4
+
5
+ gemspec
data/LICENSE.md ADDED
@@ -0,0 +1,14 @@
1
+ Copyright (c) 2009-2011 Wayne E. Seguin
2
+
3
+ Licensed under the Apache License, Version 2.0 (the "License");
4
+ you may not use this file except in compliance with the License.
5
+ You may obtain a copy of the License at
6
+
7
+ http://www.apache.org/licenses/LICENSE-2.0
8
+
9
+ Unless required by applicable law or agreed to in writing, software
10
+ distributed under the License is distributed on an "AS IS" BASIS,
11
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ See the License for the specific language governing permissions and
13
+ limitations under the License.
14
+
data/README.md ADDED
@@ -0,0 +1,43 @@
1
+ # Contributors stats
2
+
3
+ [![Build Status](https://travis-ci.org/mpapis/contributors_stats.png?branch=master)](https://travis-ci.org/mpapis/contributors_stats)
4
+ [![Code Climate](https://codeclimate.com/github/mpapis/contributors_stats.png)](https://codeclimate.com/github/mpapis/contributors_stats)
5
+ [![Dependency Status](https://gemnasium.com/mpapis/contributors_stats.png)](https://gemnasium.com/mpapis/contributors_stats)
6
+
7
+ Update static files with contributors statistics.
8
+
9
+ * Static content update of contributors statistics.
10
+ * Plugable:
11
+ ** backends to load data
12
+ ** user details calculation
13
+ ** output fomratters
14
+ ** target files updating
15
+
16
+ ## Usage
17
+
18
+ Contributors stats consists on two basic parts and few plugins.
19
+
20
+ ```ruby
21
+ calculator = ContributorsStats.load(<options>)
22
+ calculator.load(:<type> => '<name>')
23
+ calculator.user_data_type = :<user_data_type>
24
+ formatter = calculator.format(:<format>)
25
+ formatter.save('<file>', ...)
26
+ formatter.update('<file>', ..., options: <options>)
27
+ puts formatter.content
28
+ ```
29
+
30
+ Plugins are loaded automatically, for example `calculator.user_data_type = :fetch`
31
+ will load `plugins/contributors_stats/user_data/fetch.rb` and use
32
+ `ContributorsStats::UserData::Fetch` and use it to process user details calculation.
33
+
34
+ ## Examples
35
+
36
+ ```ruby
37
+ ContributorsStats.load(gh_org: 'railsisntaller').format(:markdown).save('public/contributors.md')
38
+ ContributorsStats.load(gh_repo: 'railsisntaller/website').format.update('public/index.html')
39
+ ```
40
+
41
+ ```shell
42
+ ruby -I lib:lib/plugins -rcontributors_stats -e "puts ContributorsStats.load(:gh_org => 'rvm', :gh_repo => ['wayneeseguin/rvm', 'wayneeseguin/rvm-capistrano'], :user_data => :fetch).format(:html).content"
43
+ ```
data/Rakefile ADDED
@@ -0,0 +1,28 @@
1
+ require "rake/testtask"
2
+ require "yard"
3
+ require "yard/rake/yardoc_task"
4
+
5
+ Rake::TestTask.new do |t|
6
+ t.verbose = true
7
+ t.libs.push("test")
8
+ t.pattern = "test/**/*_test.rb"
9
+ end
10
+
11
+ YARD::Rake::YardocTask.new do |t|
12
+ t.before = Proc.new do
13
+ puts "\n[YARD] Generating documentation\n\n"
14
+ end
15
+ t.files = ['lib/**/*.rb']
16
+ # --quiet / --verbose =>
17
+ t.options = ['--quiet', '--list-undoc', '--compact', '--verbose']
18
+ end
19
+
20
+ task :default => [:test, :yard]
21
+
22
+ task :example_data do
23
+ require "example_data"
24
+ example = ExampleData.new
25
+ example.parse_org( example.url_builder("orgs/railsinstaller/repos") )
26
+ example.parse_repo( example.url_builder("repos/rvm/pluginator/contributors") )
27
+ example.parse_repo( example.url_builder("repos/mpapis/rubygems-bundler/contributors") )
28
+ end
@@ -0,0 +1,24 @@
1
+ # -*- encoding: utf-8 -*-
2
+
3
+ require File.expand_path("../lib/contributors_stats/version.rb", __FILE__)
4
+
5
+ Gem::Specification.new do |spec|
6
+ spec.name = "contributors_stats"
7
+ spec.version = ContributorsStats::VERSION
8
+ spec.license = 'Apache'
9
+ spec.author = "Michal Papis"
10
+ spec.email = "mpapis@gmail.com"
11
+ spec.homepage = "https://github.com/mpapis/contributors_stats"
12
+ spec.summary = %q{Calculate statics for multiple project contributoions.}
13
+
14
+ spec.files = `git ls-files`.split("\n")
15
+ spec.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
16
+
17
+ spec.required_ruby_version = ">= 2.0.0"
18
+
19
+ spec.add_dependency "pluginator"
20
+
21
+ %w{rake simplecov coveralls yard redcarpet}.each do |name|
22
+ spec.add_development_dependency(name)
23
+ end
24
+ end
@@ -0,0 +1,28 @@
1
+ #/usr/bin/env ruby
2
+
3
+ ##
4
+ # static content update for speed and avoiding GH limits
5
+ # read GitHub contributions, transform to urls and update files
6
+ # all public methods return self for easy chaining
7
+ #
8
+ # Example:
9
+ #
10
+ # ContributorsStats.load(org: 'railsisntaller').format.update('public/contributors.html')
11
+ # contributors = ContributorsStats.load(repo: 'railsisntaller/website')
12
+ # contributors.format(:html).update('public/index.html', 'public/contributors.html')
13
+ # contributors.format(:markdown).save('public/contriutors.md')
14
+ #
15
+
16
+ require 'pluginator'
17
+ require 'contributors_stats/calculator'
18
+
19
+ # Calculate contribution statistics for projects,
20
+ # by default supports github organizations and repositories,
21
+ # but is easily extendible with plugins.
22
+ module ContributorsStats
23
+ # Initialize ContributorsStats
24
+ # @see ContributorsStats::Calculator.new
25
+ def self.load(options = {})
26
+ ContributorsStats::Calculator.new(options)
27
+ end
28
+ end
@@ -0,0 +1,29 @@
1
+ module ContributorsStats
2
+
3
+ # Basis for ContributorsStats, includes logging and plugins support
4
+ class Base
5
+ attr_accessor :logger, :options
6
+
7
+ def initialize(options = {})
8
+ @logger = $stdout
9
+ @logger = options.delete(:logger) if options[:logger]
10
+ @options = options
11
+ end
12
+
13
+ private
14
+
15
+ def filter_options(type)
16
+ @options.select do |key, value|
17
+ plugins.class_exist?(type, key)
18
+ end
19
+ end
20
+
21
+ def plugins
22
+ @plugins ||= Pluginator.find("contributors_stats", extends: %i{first_class class_exist})
23
+ end
24
+
25
+ def log(text)
26
+ logger.respond_to?(:info) ? logger.info(text) : logger.puts(text)
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,50 @@
1
+ require 'contributors_stats/reader'
2
+ require 'contributors_stats/formatter'
3
+
4
+ module ContributorsStats
5
+
6
+ # Placeholder for user details resolution plugins
7
+ module UserData
8
+ end
9
+
10
+ # Calculates statistics gathered from multiple sources
11
+ class Calculator < Reader
12
+ attr_accessor :user_data_type
13
+
14
+ def initialize(options = {})
15
+ @user_data_type = options.delete(:user_data) if options[:user_data]
16
+ super(options)
17
+ end
18
+
19
+ # transform calculated data into asked format
20
+ # @param type [String] name of plugin to use for formatting
21
+ # @param options [Hash] list of options for the plugin to use
22
+ # @return [ContributorsStats::Formatter]
23
+ def format(type = :html, options = {})
24
+ ContributorsStats::Formatter.new(calculated_data, type, options)
25
+ end
26
+
27
+ private
28
+
29
+ # group data, calculate contributions, sort by contributions
30
+ def calculated_data
31
+ @data ||= @raw_data.group_by { |contributor|
32
+ contributor['login']
33
+ }.map {|login, data|
34
+ log "user: #{login}"
35
+ [login, user_data(login, data)]
36
+ }.sort_by{|login, data|
37
+ [1000000/data['contributions'], login]
38
+ }
39
+ end
40
+
41
+ def contributions(data)
42
+ data.map{|repo| repo['contributions'].to_i}.inject(&:+)
43
+ end
44
+
45
+ def user_data(login, data)
46
+ @user_data_plugin ||= plugins.first_class!("user_data", user_data_type || :simple)
47
+ @user_data_plugin.load(login, data.first, contributions(data))
48
+ end
49
+ end
50
+ end
@@ -0,0 +1,57 @@
1
+ module ContributorsStats
2
+
3
+ # Placeholder for updater plugins
4
+ module Updater
5
+ end
6
+
7
+ # Takes care of formatting data and saving it
8
+ class Formatter
9
+ attr_reader :content
10
+
11
+ def initialize(data, type = :html, options = {})
12
+ format(data, type, options)
13
+ end
14
+
15
+ # overwrite target with the formatted content
16
+ # @param targets [Array] list of files to overwrite
17
+ def save(*targets)
18
+ targets.flatten.each do |file|
19
+ File.open(file, 'w') { |f| f.write(content * "\n") }
20
+ end
21
+ end
22
+
23
+ # update target with the formatted content
24
+ # @param targets [Array] list of files to update
25
+ # @param options [Hash] options are passed to updater plugin, check
26
+ def update(*targets, options: {})
27
+ targets.flatten.each do |file|
28
+ plugin = plugins.first_ask!("updater", :handles?, file)
29
+ update_file(file) do |file_content|
30
+ plugin.update(file_content, content, options)
31
+ end
32
+ end
33
+ end
34
+
35
+ private
36
+
37
+ def plugins
38
+ @plugins ||= Pluginator.find("contributors_stats", extends: %i{first_ask first_class})
39
+ end
40
+
41
+ def format(data, type = :html, options = {})
42
+ formatter_plugin = plugins.first_class!("formatter", type).new(options)
43
+ @content = data.map do |login, user_data|
44
+ formatter_plugin.format(login, user_data)
45
+ end
46
+ end
47
+
48
+ # Allow editing file text in a block
49
+ # @example
50
+ # update_file('some.txt'){|text| text.gsub(/bla/,'ble')}
51
+ def update_file(file)
52
+ text = File.read(file)
53
+ text = yield text
54
+ File.open(file, 'w') { |f| f.write(text) }
55
+ end
56
+ end
57
+ end
@@ -0,0 +1,49 @@
1
+ require 'json'
2
+ require 'open-uri'
3
+
4
+ module ContributorsStats
5
+
6
+ # Support code for loading json urls/files
7
+ module JsonHelper
8
+
9
+ # Build full path to resource to use
10
+ def url_builder(path)
11
+ "#{self.path_prefix}#{path}#{self.path_suffix}"
12
+ end
13
+
14
+ # Load json from url, fallback to prefix
15
+ def load_json(url)
16
+ open(url){ |json| JSON.load(json) }
17
+ rescue Exception => e
18
+ if File.exist?("#{self.path_prefix}#{url}")
19
+ open("#{self.path_prefix}#{url}"){ |json| JSON.load(json) }
20
+ elsif File.exist?("#{self.path_prefix}#{url}#{self.path_suffix}")
21
+ open("#{self.path_prefix}#{url}#{self.path_suffix}"){ |json| JSON.load(json) }
22
+ else
23
+ raise e
24
+ end
25
+ end
26
+
27
+ # get prefix, sets the default if empty, makes sure it's ending with '/'
28
+ def path_prefix
29
+ @path_prefix ||= "https://api.github.com"
30
+ @path_prefix+="/" if @path_prefix != "" && @path_prefix[-1] != "/"
31
+ @path_prefix
32
+ end
33
+
34
+ # get suffix, sets the default if empty, makes sure it's starts with '.'
35
+ def path_suffix
36
+ @path_suffix ||= ""
37
+ @path_suffix = ".#{@path_suffix}" if @path_suffix != "" && @path_suffix[0] != "."
38
+ @path_suffix
39
+ end
40
+
41
+ protected
42
+
43
+ def configure_path(prefix, suffix)
44
+ @path_prefix = prefix
45
+ @path_suffix = suffix
46
+ end
47
+
48
+ end
49
+ end
@@ -0,0 +1,52 @@
1
+ require 'contributors_stats/base'
2
+ require 'contributors_stats/json_helper'
3
+
4
+ module ContributorsStats
5
+
6
+ # Base for reading ContributorsStats data
7
+ class Reader < Base
8
+ include ContributorsStats::JsonHelper
9
+
10
+ attr_reader :data, :raw_data
11
+
12
+ def initialize(options = {})
13
+ configure_path(*options.delete(:configure_path)) if options[:configure_path]
14
+ super(options)
15
+ @raw_data = []
16
+ @data = nil
17
+ parse_readers(filter_options("reader"))
18
+ end
19
+
20
+ # load data using given plugin
21
+ # @param type [String] plugin to use
22
+ # @param name [String] name to pass to the plugin
23
+ def load(type, name)
24
+ reader_plugin(type).load(name) do |data, name|
25
+ log "repository: #{name}"
26
+ @raw_data += data
27
+ end
28
+ @data = nil
29
+ end
30
+
31
+ private
32
+
33
+ def reader_plugin(type)
34
+ plugin = plugins.first_class!("reader", type)
35
+ if plugin.kind_of?(ContributorsStats::JsonHelper)
36
+ plugin.send(:configure_path, path_prefix, path_suffix)
37
+ end
38
+ plugin
39
+ end
40
+
41
+ def parse_readers(options = {})
42
+ options.each do |type, name|
43
+ if name.kind_of?(Array)
44
+ name.each{|n| load(type,n)}
45
+ else
46
+ load(type, name)
47
+ end
48
+ end
49
+ end
50
+
51
+ end
52
+ end
@@ -0,0 +1,4 @@
1
+ module ContributorsStats
2
+ # gem version
3
+ VERSION = "1.0.0"
4
+ end
@@ -0,0 +1,58 @@
1
+ require 'contributors_stats/json_helper'
2
+
3
+ # Dump some test data from github, rather one time, but who knows ;)
4
+ class ExampleData
5
+ include ContributorsStats::JsonHelper
6
+
7
+ # setup proper context for dumping data
8
+ def initialize(target_path = "../../test/fixtures-gh")
9
+ target_path = File.expand_path( target_path, __FILE__ ) unless Dir.exist?(target_path)
10
+ Dir.chdir(target_path)
11
+ `rm -rf *` # clean
12
+ end
13
+
14
+ # parse organization data from given url, parses also repositories
15
+ # @see .parse_repo
16
+ def parse_org(org_url)
17
+ puts "reading: #{org_url}"
18
+ org_data = load_json(org_url)
19
+ write(org_url, org_data)
20
+ org_data.each do |repo|
21
+ parse_repo(repo['contributors_url'], " ")
22
+ end
23
+ end
24
+
25
+ # parse repository data from given url, parses also users
26
+ # @see .parse_repo
27
+ def parse_repo(contributors_url, str_prefix="")
28
+ puts "#{str_prefix}reading: #{contributors_url}"
29
+ contributors_data = load_json(contributors_url)
30
+ write(contributors_url, contributors_data)
31
+ contributors_data.each do |contributor|
32
+ parse_user(contributor['url'], str_prefix+" ")
33
+ end
34
+ end
35
+
36
+ # parse user data from given url
37
+ def parse_user(user_url, str_prefix="")
38
+ puts "#{str_prefix}reading: #{user_url}"
39
+ user_data = load_json(user_url)
40
+ write(user_url, user_data)
41
+ end
42
+
43
+ # saves content to path corresponding to the given file url
44
+ def write(file, content)
45
+ file = remove_prefix(file)
46
+ return if File.exist?(file)
47
+ `mkdir -p #{File.dirname(file)}`
48
+ File.open(file, "w+") { |f|
49
+ f.write(remove_prefix(JSON.pretty_generate(content)))
50
+ }
51
+ end
52
+
53
+ # remove prefix from all urls in the given content
54
+ def remove_prefix(conten)
55
+ conten.gsub(/#{path_prefix}([^"]*)/, "\\1.json")
56
+ end
57
+
58
+ end