metior 0.1.4 → 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/.travis.yml +2 -0
- data/.yardopts +1 -0
- data/Changelog.md +23 -0
- data/Gemfile +1 -17
- data/Gemfile.lock +28 -21
- data/README.md +66 -20
- data/lib/metior.rb +30 -14
- data/lib/metior/actor.rb +28 -22
- data/lib/metior/auto_include_vcs.rb +43 -0
- data/lib/metior/collections/actor_collection.rb +97 -0
- data/lib/metior/collections/collection.rb +84 -0
- data/lib/metior/collections/commit_collection.rb +309 -0
- data/lib/metior/commit.rb +128 -48
- data/lib/metior/errors.rb +2 -2
- data/lib/metior/git/commit.rb +32 -31
- data/lib/metior/git/repository.rb +71 -6
- data/lib/metior/github/commit.rb +5 -16
- data/lib/metior/github/repository.rb +68 -40
- data/lib/metior/report.rb +139 -0
- data/lib/metior/report/view.rb +120 -0
- data/lib/metior/report/view_helper.rb +47 -0
- data/lib/metior/repository.rb +225 -56
- data/lib/metior/vcs.rb +12 -3
- data/lib/metior/version.rb +1 -1
- data/metior.gemspec +28 -26
- data/reports/default.rb +17 -0
- data/reports/default/images/favicon.png +0 -0
- data/reports/default/stylesheets/default.css +128 -0
- data/reports/default/templates/actor/minimal.mustache +1 -0
- data/reports/default/templates/commit/minimal.mustache +1 -0
- data/reports/default/templates/index.mustache +27 -0
- data/reports/default/templates/most_significant_authors.mustache +11 -0
- data/reports/default/templates/most_significant_commits.mustache +13 -0
- data/reports/default/templates/repository_information.mustache +17 -0
- data/reports/default/templates/top_committers.mustache +11 -0
- data/reports/default/views/index.rb +33 -0
- data/reports/default/views/most_significant_authors.rb +19 -0
- data/reports/default/views/most_significant_commits.rb +19 -0
- data/reports/default/views/repository_information.rb +47 -0
- data/reports/default/views/top_committers.rb +21 -0
- data/test/fixtures.rb +54 -36
- data/test/helper.rb +10 -3
- data/test/{test_class_loading.rb → test_1st_class_loading.rb} +1 -1
- data/test/test_actor_colletion.rb +78 -0
- data/test/test_collection.rb +61 -0
- data/test/test_commit_collection.rb +139 -0
- data/test/test_git.rb +58 -5
- data/test/test_github.rb +52 -9
- data/test/test_metior.rb +22 -1
- data/test/test_report.rb +49 -0
- data/test/test_repository.rb +46 -9
- data/test/test_vcs.rb +36 -13
- metadata +105 -43
data/lib/metior/github/commit.rb
CHANGED
@@ -6,8 +6,6 @@
|
|
6
6
|
require 'time'
|
7
7
|
|
8
8
|
require 'metior/commit'
|
9
|
-
require 'metior/github'
|
10
|
-
require 'metior/github/actor'
|
11
9
|
|
12
10
|
module Metior
|
13
11
|
|
@@ -18,16 +16,13 @@ module Metior
|
|
18
16
|
# @author Sebastian Staudt
|
19
17
|
class Commit < Metior::Commit
|
20
18
|
|
21
|
-
include Metior::GitHub
|
22
|
-
|
23
19
|
# Creates a new GitHub commit object linked to the repository and branch
|
24
20
|
# it belongs to and the data parsed from the corresponding JSON data
|
25
21
|
#
|
26
22
|
# @param [Repository] repo The GitHub repository this commit belongs to
|
27
|
-
# @param [String] range The commit range this commits belongs to
|
28
23
|
# @param [Hashie:Mash] commit The commit data parsed from the JSON API
|
29
|
-
def initialize(repo,
|
30
|
-
super repo
|
24
|
+
def initialize(repo, commit)
|
25
|
+
super repo
|
31
26
|
|
32
27
|
@added_files = []
|
33
28
|
@additions = 0
|
@@ -38,16 +33,10 @@ module Metior
|
|
38
33
|
@id = commit.id
|
39
34
|
@message = commit.message
|
40
35
|
@modified_files = []
|
36
|
+
@parents = commit.parents.map { |parent| parent.id }
|
41
37
|
|
42
|
-
|
43
|
-
|
44
|
-
@author = Actor.new repo, commit.author if author.nil?
|
45
|
-
@author.add_commit self
|
46
|
-
|
47
|
-
committers = repo.committers range
|
48
|
-
@committer = committers[Actor.id_for commit.committer]
|
49
|
-
@committer = Actor.new repo, commit.committer if @committer.nil?
|
50
|
-
@committer.add_commit self
|
38
|
+
self.author = commit.author
|
39
|
+
self.committer = commit.committer
|
51
40
|
end
|
52
41
|
|
53
42
|
end
|
@@ -5,8 +5,6 @@
|
|
5
5
|
|
6
6
|
require 'octokit'
|
7
7
|
|
8
|
-
require 'metior/github'
|
9
|
-
require 'metior/github/commit'
|
10
8
|
require 'metior/repository'
|
11
9
|
|
12
10
|
module Metior
|
@@ -18,8 +16,6 @@ module Metior
|
|
18
16
|
# @author Sebastian Staudt
|
19
17
|
class Repository < Metior::Repository
|
20
18
|
|
21
|
-
include Metior::GitHub
|
22
|
-
|
23
19
|
# @return [String] The project name of the repository
|
24
20
|
attr_reader :project
|
25
21
|
|
@@ -31,23 +27,42 @@ module Metior
|
|
31
27
|
#
|
32
28
|
# @param [String] user The GitHub username of repository's owner
|
33
29
|
# @param [String] project The name of the project
|
34
|
-
def initialize(user, project)
|
30
|
+
def initialize(user, project = nil)
|
31
|
+
user, project = user.split('/') if user.include? '/'
|
32
|
+
|
35
33
|
super "#{user}/#{project}"
|
36
34
|
|
37
|
-
@project
|
38
|
-
@user
|
35
|
+
@project = project
|
36
|
+
@user = user
|
39
37
|
end
|
40
38
|
|
41
39
|
private
|
42
40
|
|
41
|
+
# Returns the unique identifier for the commit the given reference – like
|
42
|
+
# a branch name – is pointing to
|
43
|
+
#
|
44
|
+
# Returns the given ref name immediately if it is a full SHA1 commit ID.
|
45
|
+
#
|
46
|
+
# @param [String] ref A symbolic reference name
|
47
|
+
# @return [String] The SHA1 ID of the commit the reference is pointing to
|
48
|
+
def id_for_ref(ref)
|
49
|
+
return ref if ref.match(/[0-9a-f]{40}/)
|
50
|
+
@refs[ref] = Octokit.commit(@path, ref).id unless @refs.key? ref
|
51
|
+
@refs[ref]
|
52
|
+
end
|
53
|
+
|
54
|
+
# Loads all branches and the corresponding commit IDs of this repository
|
55
|
+
#
|
56
|
+
# @return [Hash<String, String>] The names of all branches and the
|
57
|
+
# corresponding commit IDs
|
58
|
+
# @see Octokit#branches
|
59
|
+
def load_branches
|
60
|
+
Octokit.branches(@path)
|
61
|
+
end
|
62
|
+
|
43
63
|
# This method uses Octokit to load all commits from the given commit
|
44
64
|
# range
|
45
65
|
#
|
46
|
-
# If you want to compare a branch with another (i.e. if you supply a
|
47
|
-
# range of commits), it needs two calls to the GitHub API to get all
|
48
|
-
# commits of each branch. The comparison is done in the code, so the
|
49
|
-
# limits (see below) will be effectively cut in half.
|
50
|
-
#
|
51
66
|
# @note GitHub API is currently limited to 60 calls a minute, so you
|
52
67
|
# won't be able to query branches with more than 2100 commits
|
53
68
|
# (35 commits per call).
|
@@ -56,42 +71,55 @@ module Metior
|
|
56
71
|
# (`'master..development'`), a range (`'master'..'development'`)
|
57
72
|
# or as a single ref (`'master'`). A single ref name means all
|
58
73
|
# commits reachable from that ref.
|
59
|
-
# @return [
|
60
|
-
#
|
61
|
-
|
62
|
-
commits = load_ref_commits(range.last)
|
63
|
-
if range.first == ''
|
64
|
-
base_commits = []
|
65
|
-
else
|
66
|
-
base_commits = load_ref_commits(range.first).map! do |commit|
|
67
|
-
commit.id
|
68
|
-
end
|
69
|
-
end
|
70
|
-
commits.reject { |commit| base_commits.include? commit.id }
|
71
|
-
end
|
72
|
-
|
73
|
-
# This method uses Octokit to load all commits from the given ref
|
74
|
-
#
|
75
|
-
# Because of GitHub API limitations, the commits have to be loaded in
|
76
|
-
# batches.
|
77
|
-
#
|
78
|
-
# @note GitHub API is currently limited to 60 calls a minute, so you
|
79
|
-
# won't be able to query refs with more than 2100 commits (35
|
80
|
-
# commits per call).
|
81
|
-
# @param [String] ref The ref to load commits from
|
82
|
-
# @return [Array<Commit>] All commits from the given ref
|
74
|
+
# @return [Hashie::Mash, nil] The base commit of the requested range or
|
75
|
+
# `nil` if the the range starts at the beginning of the history
|
76
|
+
# @return [Array<Hashie::Mash>] All commits in the given commit range
|
83
77
|
# @see Octokit::Commits#commits
|
84
|
-
def
|
78
|
+
def load_commits(range)
|
79
|
+
base_commit = nil
|
85
80
|
commits = []
|
86
81
|
page = 1
|
87
82
|
begin
|
88
83
|
loop do
|
89
|
-
|
84
|
+
new_commits = Octokit.commits(@path, range.last, :page => page)
|
85
|
+
base_commit_index = new_commits.find_index do |commit|
|
86
|
+
commit.id == range.first
|
87
|
+
end
|
88
|
+
unless base_commit_index.nil?
|
89
|
+
commits += new_commits[0..base_commit_index-1]
|
90
|
+
base_commit = new_commits[base_commit_index]
|
91
|
+
break
|
92
|
+
end
|
93
|
+
commits += new_commits
|
90
94
|
page += 1
|
91
95
|
end
|
92
|
-
rescue Octokit::NotFound
|
96
|
+
rescue Octokit::NotFound
|
93
97
|
end
|
94
|
-
|
98
|
+
|
99
|
+
[base_commit, commits]
|
100
|
+
end
|
101
|
+
|
102
|
+
# Loads both the name and description of the project contained in the
|
103
|
+
# repository from GitHub
|
104
|
+
#
|
105
|
+
# @see #description
|
106
|
+
# @see #name
|
107
|
+
# @see Octokit.repo
|
108
|
+
def load_name_and_description
|
109
|
+
github_repo = Octokit.repo @path
|
110
|
+
@description = github_repo.description
|
111
|
+
@name = github_repo.name
|
112
|
+
end
|
113
|
+
alias_method :load_description, :load_name_and_description
|
114
|
+
alias_method :load_name, :load_name_and_description
|
115
|
+
|
116
|
+
# Loads all tags and the corresponding commit IDs of this repository
|
117
|
+
#
|
118
|
+
# @return [Hash<String, String>] The names of all tags and the
|
119
|
+
# corresponding commit IDs
|
120
|
+
# @see Octokit#tags
|
121
|
+
def load_tags
|
122
|
+
Octokit.tags @path
|
95
123
|
end
|
96
124
|
|
97
125
|
end
|
@@ -0,0 +1,139 @@
|
|
1
|
+
# This code is free software; you can redistribute it and/or modify it under
|
2
|
+
# the terms of the new BSD License.
|
3
|
+
#
|
4
|
+
# Copyright (c) 2011, Sebastian Staudt
|
5
|
+
|
6
|
+
require 'fileutils'
|
7
|
+
require 'mustache'
|
8
|
+
|
9
|
+
require 'metior/report/view'
|
10
|
+
|
11
|
+
module Metior
|
12
|
+
|
13
|
+
# This class represents a report
|
14
|
+
#
|
15
|
+
# A report is a collection of Mustache views that have access to the
|
16
|
+
# repository attached to this report.
|
17
|
+
#
|
18
|
+
# @author Sebastian Staudt
|
19
|
+
# @see View
|
20
|
+
class Report
|
21
|
+
|
22
|
+
# The path where the reports bundled with Metior live
|
23
|
+
REPORTS_PATH = File.expand_path File.join File.dirname(__FILE__), '..', '..', 'reports'
|
24
|
+
|
25
|
+
# Returns the range of commits that should be analyzed by this report
|
26
|
+
#
|
27
|
+
# @return [String, Range] The range of commits covered by this report
|
28
|
+
attr_reader :range
|
29
|
+
|
30
|
+
# Returns the repository that should be analyzed by this report
|
31
|
+
#
|
32
|
+
# @return [Repository] The repository attached to this report
|
33
|
+
attr_reader :repository
|
34
|
+
|
35
|
+
# Create a new report instance for the given report name, repository and
|
36
|
+
# commit range
|
37
|
+
#
|
38
|
+
# @param [String, Symbol] name The name of the report to load and
|
39
|
+
# initialize
|
40
|
+
# @param [Repository] repository The repository to analyze
|
41
|
+
# @param [String, Range] range The commit range to analyze
|
42
|
+
def self.create(name, repository, range = repository.vcs::DEFAULT_BRANCH)
|
43
|
+
require File.join(REPORTS_PATH, name.to_s)
|
44
|
+
name = name.to_s.split('_').map { |n| n.capitalize }.join('')
|
45
|
+
const_get(name.to_sym).new(repository, range)
|
46
|
+
end
|
47
|
+
|
48
|
+
# Returns the name of this report
|
49
|
+
#
|
50
|
+
# @return [String] The name of this report
|
51
|
+
def self.name
|
52
|
+
class_variable_get(:@@name).to_s
|
53
|
+
end
|
54
|
+
|
55
|
+
# Returns the file system path this report resides in
|
56
|
+
#
|
57
|
+
# @return [String] The path of this report
|
58
|
+
def self.path
|
59
|
+
File.join REPORTS_PATH, name
|
60
|
+
end
|
61
|
+
|
62
|
+
# Returns the file system path this report's templates reside in
|
63
|
+
#
|
64
|
+
# @return [String] The path of this report's templates
|
65
|
+
def self.template_path
|
66
|
+
File.join path, 'templates'
|
67
|
+
end
|
68
|
+
|
69
|
+
# Returns the file system path this report's views reside in
|
70
|
+
#
|
71
|
+
# @return [String] The path of this report's views
|
72
|
+
def self.view_path
|
73
|
+
File.join path, 'views'
|
74
|
+
end
|
75
|
+
|
76
|
+
# Returns the symbolic names of the main views this report consists of
|
77
|
+
#
|
78
|
+
# @reeturn [Array<Symbol>] This report's views
|
79
|
+
def self.views
|
80
|
+
class_variable_get :@@views
|
81
|
+
end
|
82
|
+
|
83
|
+
# Creates a new report for the given repository and commit range
|
84
|
+
#
|
85
|
+
# @param [Repository] repository The repository to analyze
|
86
|
+
# @param [String, Range] range The commit range to analyze
|
87
|
+
def initialize(repository, range = repository.vcs::DEFAULT_BRANCH)
|
88
|
+
@range = range
|
89
|
+
@repository = repository
|
90
|
+
end
|
91
|
+
|
92
|
+
# Generates this report's output into the given target directory
|
93
|
+
#
|
94
|
+
# This will generate individual HTML files for the main views of the
|
95
|
+
# report.
|
96
|
+
#
|
97
|
+
# @param [String] target_dir The target directory of the report
|
98
|
+
def generate(target_dir)
|
99
|
+
target_dir = File.expand_path target_dir
|
100
|
+
copy_assets(target_dir)
|
101
|
+
|
102
|
+
Mustache.template_path = self.class.template_path
|
103
|
+
Mustache.view_path = self.class.view_path
|
104
|
+
Mustache.view_namespace = self.class
|
105
|
+
|
106
|
+
self.class.views.each do |view_name|
|
107
|
+
file_name = File.join target_dir, view_name.to_s.downcase + '.html'
|
108
|
+
begin
|
109
|
+
output_file = File.open file_name, 'w'
|
110
|
+
output_file.write Mustache.view_class(view_name).new(self).render
|
111
|
+
ensure
|
112
|
+
output_file.close
|
113
|
+
end
|
114
|
+
end
|
115
|
+
end
|
116
|
+
|
117
|
+
private
|
118
|
+
|
119
|
+
# Copies the assets coming with this report to the given target directory
|
120
|
+
#
|
121
|
+
# This will copy the contents of the `images`, `javascript` and
|
122
|
+
# `stylesheets` directories inside the report's path into the target
|
123
|
+
# directory.
|
124
|
+
#
|
125
|
+
# @param [String] target_dir The target directory of the report
|
126
|
+
def copy_assets(target_dir)
|
127
|
+
FileUtils.mkdir_p target_dir
|
128
|
+
|
129
|
+
%w{images javascripts stylesheets}.map do |type|
|
130
|
+
File.join(self.class.path, type)
|
131
|
+
end.each do |src|
|
132
|
+
next unless File.directory? src
|
133
|
+
FileUtils.cp_r src, target_dir
|
134
|
+
end
|
135
|
+
end
|
136
|
+
|
137
|
+
end
|
138
|
+
|
139
|
+
end
|
@@ -0,0 +1,120 @@
|
|
1
|
+
# This code is free software; you can redistribute it and/or modify it under
|
2
|
+
# the terms of the new BSD License.
|
3
|
+
#
|
4
|
+
# Copyright (c) 2011, Sebastian Staudt
|
5
|
+
|
6
|
+
require 'metior/report/view_helper'
|
7
|
+
|
8
|
+
class Metior::Report
|
9
|
+
|
10
|
+
# This class is an extended Mustache view
|
11
|
+
#
|
12
|
+
# A view represents a whole page or a section of a page that displays
|
13
|
+
# information about a repository. It is always attached to a specific report
|
14
|
+
# and can access the information of the report and the repository.
|
15
|
+
#
|
16
|
+
# @author Sebastian Staudt
|
17
|
+
class View < Mustache
|
18
|
+
|
19
|
+
include ViewHelper
|
20
|
+
|
21
|
+
# This will initialize new view classes
|
22
|
+
#
|
23
|
+
# @param [Class] subclass The inheriting view class
|
24
|
+
def self.inherited(subclass)
|
25
|
+
subclass.send :class_variable_set, :@@required_features, []
|
26
|
+
end
|
27
|
+
|
28
|
+
# Specifies one or more VCS features that are required to generate this
|
29
|
+
# view
|
30
|
+
#
|
31
|
+
# @example
|
32
|
+
# class LineStatsView < View
|
33
|
+
#
|
34
|
+
# requires :line_stats
|
35
|
+
#
|
36
|
+
# ...
|
37
|
+
#
|
38
|
+
# end
|
39
|
+
# @param [Symbol, ...] features One ore more features that are required for
|
40
|
+
# this view
|
41
|
+
def self.requires(*features)
|
42
|
+
required_features = class_variable_get :@@required_features
|
43
|
+
required_features += features
|
44
|
+
class_variable_set :@@required_features, required_features
|
45
|
+
end
|
46
|
+
|
47
|
+
# Initializes this view with the given report
|
48
|
+
#
|
49
|
+
# @param [Report] report The report this view belongs to
|
50
|
+
def initialize(report)
|
51
|
+
@report = report
|
52
|
+
end
|
53
|
+
|
54
|
+
# This will try to render a view as a partial of the current view or call a
|
55
|
+
# method of the repository
|
56
|
+
#
|
57
|
+
# The partial view will either be aquired from the current view namespace,
|
58
|
+
# i.e. the report this view belongs to, or from the default report.
|
59
|
+
#
|
60
|
+
# @param [Symbol] name The name of the view to render or the repository
|
61
|
+
# method to call
|
62
|
+
# @param [Object, ...] args The arguments to pass to the method
|
63
|
+
# @param [Proc] block The block to pass to the method
|
64
|
+
# @see Default
|
65
|
+
# @see http://rubydoc.info/gems/mustache/Mustache#view_class-class_method
|
66
|
+
# Mustache.view_class
|
67
|
+
def method_missing(name, *args, &block)
|
68
|
+
view_class = Mustache.view_class name
|
69
|
+
return view_class.new(@report).render if view_class != Mustache
|
70
|
+
|
71
|
+
repository.send name, *args, &block
|
72
|
+
end
|
73
|
+
|
74
|
+
# This checks if all required VCS features of this view are available for
|
75
|
+
# this report's repository
|
76
|
+
#
|
77
|
+
# @param [Object, ...] args The arguments expected by {Mustache#render}
|
78
|
+
# @see .requires
|
79
|
+
# @see http://rubydoc.info/gems/mustache/Mustache#render-instance_method
|
80
|
+
# Mustache#render
|
81
|
+
def render(*args)
|
82
|
+
features = self.class.send :class_variable_get, :@@required_features
|
83
|
+
super if features.all? { |feature| repository.supports? feature }
|
84
|
+
end
|
85
|
+
|
86
|
+
# Returns the repository that is analyzed by the report this view belongs
|
87
|
+
# to
|
88
|
+
#
|
89
|
+
# @return [Repository] The repository belonging to this view's report
|
90
|
+
def repository
|
91
|
+
@report.repository
|
92
|
+
end
|
93
|
+
|
94
|
+
# Returns whether the given name refers a partial view that can be rendered
|
95
|
+
# or method that can be called
|
96
|
+
#
|
97
|
+
# This checks whether this view has a method with the given name, or if
|
98
|
+
# another view with this name exists, or if the repository has a method
|
99
|
+
# with this name.
|
100
|
+
#
|
101
|
+
# @param [Symbol] name The name of the partial or method
|
102
|
+
# @return [Boolean] `true` if the given name refers a partial or method
|
103
|
+
# @see http://rubydoc.info/gems/mustache/Mustache#view_class-class_method
|
104
|
+
# Mustache.view_class
|
105
|
+
def respond_to?(name)
|
106
|
+
methods.include?(name.to_s) ||
|
107
|
+
Mustache.view_class(name) != Mustache ||
|
108
|
+
repository.respond_to?(name)
|
109
|
+
end
|
110
|
+
|
111
|
+
# Returns the name of the VCS the analyzed repository is using
|
112
|
+
#
|
113
|
+
# @return [Symbol] The name of the current VCS
|
114
|
+
def vcs_name
|
115
|
+
repository.vcs::NAME
|
116
|
+
end
|
117
|
+
|
118
|
+
end
|
119
|
+
|
120
|
+
end
|