metior 0.1.1
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 +4 -0
- data/.travis.yml +7 -0
- data/.yardopts +4 -0
- data/Changelog.md +22 -0
- data/Gemfile +21 -0
- data/Gemfile.lock +44 -0
- data/LICENSE +25 -0
- data/README.md +117 -0
- data/Rakefile +48 -0
- data/lib/core_ext/object.rb +25 -0
- data/lib/metior.rb +50 -0
- data/lib/metior/actor.rb +85 -0
- data/lib/metior/commit.rb +118 -0
- data/lib/metior/errors.rb +19 -0
- data/lib/metior/git.rb +25 -0
- data/lib/metior/git/actor.rb +43 -0
- data/lib/metior/git/commit.rb +68 -0
- data/lib/metior/git/repository.rb +72 -0
- data/lib/metior/github.rb +27 -0
- data/lib/metior/github/actor.rb +43 -0
- data/lib/metior/github/commit.rb +55 -0
- data/lib/metior/github/repository.rb +99 -0
- data/lib/metior/repository.rb +273 -0
- data/lib/metior/vcs.rb +150 -0
- data/lib/metior/version.rb +11 -0
- data/metior.gemspec +26 -0
- data/test/fixtures/mojombo-grit-master-1b2fe77.txt +0 -0
- data/test/helper.rb +26 -0
- data/test/mock_vcs/actor.rb +28 -0
- data/test/mock_vcs/commit.rb +53 -0
- data/test/mock_vcs/repository.rb +72 -0
- data/test/test_class_loading.rb +61 -0
- data/test/test_git.rb +22 -0
- data/test/test_github.rb +43 -0
- data/test/test_metior.rb +34 -0
- data/test/test_repository.rb +150 -0
- data/test/test_vcs.rb +40 -0
- metadata +223 -0
@@ -0,0 +1,99 @@
|
|
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 'octokit'
|
7
|
+
|
8
|
+
require 'metior/github'
|
9
|
+
require 'metior/github/commit'
|
10
|
+
require 'metior/repository'
|
11
|
+
|
12
|
+
module Metior
|
13
|
+
|
14
|
+
module GitHub
|
15
|
+
|
16
|
+
# Represents a GitHub source code repository
|
17
|
+
#
|
18
|
+
# @author Sebastian Staudt
|
19
|
+
class Repository < Metior::Repository
|
20
|
+
|
21
|
+
include Metior::GitHub
|
22
|
+
|
23
|
+
# @return [String] The project name of the repository
|
24
|
+
attr_reader :project
|
25
|
+
|
26
|
+
# @return [String] The GitHub username of the repository's owner
|
27
|
+
attr_reader :user
|
28
|
+
|
29
|
+
# Creates a new GitHub repository based on the given user and project
|
30
|
+
# names
|
31
|
+
#
|
32
|
+
# @param [String] user The GitHub username of repository's owner
|
33
|
+
# @param [String] project The name of the project
|
34
|
+
def initialize(user, project)
|
35
|
+
super "#{user}/#{project}"
|
36
|
+
|
37
|
+
@project = project
|
38
|
+
@user = user
|
39
|
+
end
|
40
|
+
|
41
|
+
private
|
42
|
+
|
43
|
+
# This method uses Octokit to load all commits from the given commit
|
44
|
+
# range
|
45
|
+
#
|
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
|
+
# @note GitHub API is currently limited to 60 calls a minute, so you
|
52
|
+
# won't be able to query branches with more than 2100 commits
|
53
|
+
# (35 commits per call).
|
54
|
+
# @param [String, Range] range The range of commits for which the commits
|
55
|
+
# should be loaded. This may be given as a string
|
56
|
+
# (`'master..development'`), a range (`'master'..'development'`)
|
57
|
+
# or as a single ref (`'master'`). A single ref name means all
|
58
|
+
# commits reachable from that ref.
|
59
|
+
# @return [Array<Commit>] All commits in the given commit range
|
60
|
+
# @see #load_branch_commits
|
61
|
+
def load_commits(range)
|
62
|
+
commits = load_branch_commits(range.last, range)
|
63
|
+
base_commits = load_branch_commits(range.first, range).map! do |commit|
|
64
|
+
commit.id
|
65
|
+
end
|
66
|
+
commits.reject { |commit| base_commits.include? commit.id }
|
67
|
+
end
|
68
|
+
|
69
|
+
# This method uses Octokit to load all commits from the given branch
|
70
|
+
#
|
71
|
+
# Because of GitHub API limitations, the commits have to be loaded in
|
72
|
+
# batches.
|
73
|
+
#
|
74
|
+
# @note GitHub API is currently limited to 60 calls a minute, so you
|
75
|
+
# won't be able to query branches with more than 2100 commits
|
76
|
+
# (35 commits per call).
|
77
|
+
# @param [String] branch The branch to load commits from
|
78
|
+
# @param [String, Range] range The range of commits to which the loaded
|
79
|
+
# commits should be assigned
|
80
|
+
# @return [Array<Commit>] All commits from the given branch
|
81
|
+
# @see Octokit::Commits#commits
|
82
|
+
def load_branch_commits(branch, range)
|
83
|
+
commits = []
|
84
|
+
page = 1
|
85
|
+
begin
|
86
|
+
begin
|
87
|
+
commits += Octokit.commits(@path, range, :page => page)
|
88
|
+
page += 1
|
89
|
+
end while true
|
90
|
+
rescue Octokit::NotFound
|
91
|
+
end
|
92
|
+
commits
|
93
|
+
end
|
94
|
+
|
95
|
+
end
|
96
|
+
|
97
|
+
end
|
98
|
+
|
99
|
+
end
|
@@ -0,0 +1,273 @@
|
|
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/actor'
|
7
|
+
|
8
|
+
module Metior
|
9
|
+
|
10
|
+
# This class represents a source code repository.
|
11
|
+
#
|
12
|
+
# @abstract It has to be subclassed to implement a repository representation
|
13
|
+
# for a specific VCS.
|
14
|
+
# @author Sebastian Staudt
|
15
|
+
class Repository
|
16
|
+
|
17
|
+
# @return [String] The file system path of this repository
|
18
|
+
attr_reader :path
|
19
|
+
|
20
|
+
# Creates a new repository instance with the given file system path
|
21
|
+
#
|
22
|
+
# @param [String] path The file system path of the repository
|
23
|
+
def initialize(path)
|
24
|
+
@authors = {}
|
25
|
+
@commits = {}
|
26
|
+
@committers = {}
|
27
|
+
@path = path
|
28
|
+
end
|
29
|
+
|
30
|
+
# Returns all authors from the given commit range in a hash where the IDs
|
31
|
+
# of the authors are the keys and the authors are the values
|
32
|
+
#
|
33
|
+
# This will call `commits(range)` if the authors for the commit range are
|
34
|
+
# not known yet.
|
35
|
+
#
|
36
|
+
# @param [String, Range] range The range of commits for which the authors
|
37
|
+
# should be retrieved. This may be given as a string
|
38
|
+
# (`'master..development'`), a range (`'master'..'development'`) or
|
39
|
+
# as a single ref (`'master'`). A single ref name means all commits
|
40
|
+
# reachable from that ref.
|
41
|
+
# @return [Hash<String, Actor>] All authors from the given commit range
|
42
|
+
# @see #commits
|
43
|
+
def authors(range = self.class::DEFAULT_BRANCH)
|
44
|
+
range = parse_range range
|
45
|
+
commits(range) if @authors[range].nil?
|
46
|
+
@authors[range]
|
47
|
+
end
|
48
|
+
alias_method :contributors, :authors
|
49
|
+
|
50
|
+
# Loads all commits including their committers and authors from the given
|
51
|
+
# commit range
|
52
|
+
#
|
53
|
+
# @param [String, Range] range The range of commits for which the commits
|
54
|
+
# should be retrieved. This may be given as a string
|
55
|
+
# (`'master..development'`), a range (`'master'..'development'`) or
|
56
|
+
# as a single ref (`'master'`). A single ref name means all commits
|
57
|
+
# reachable from that ref.
|
58
|
+
# @return [Array<Commit>] All commits from the given commit range
|
59
|
+
def commits(range = self.class::DEFAULT_BRANCH)
|
60
|
+
range = parse_range range
|
61
|
+
if @commits[range].nil?
|
62
|
+
@authors[range] = {}
|
63
|
+
@committers[range] = {}
|
64
|
+
@commits[range] = []
|
65
|
+
load_commits(range).each do |commit|
|
66
|
+
commit = self.class::Commit.new(self, range, commit)
|
67
|
+
@commits[range] << commit
|
68
|
+
@authors[range][commit.author.id] = commit.author
|
69
|
+
@committers[range][commit.committer.id] = commit.committer
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
@commits[range]
|
74
|
+
end
|
75
|
+
|
76
|
+
# Returns all committers from the given commit range in a hash where the
|
77
|
+
# IDs of the committers are the keys and the committers are the values
|
78
|
+
#
|
79
|
+
# This will call `commits(range)` if the committers for the commit range
|
80
|
+
# are not known yet.
|
81
|
+
#
|
82
|
+
# @param [String, Range] range The range of commits for which the
|
83
|
+
# committers should be retrieved. This may be given as a string
|
84
|
+
# (`'master..development'`), a range (`'master'..'development'`) or
|
85
|
+
# as a single ref (`'master'`). A single ref name means all commits
|
86
|
+
# reachable from that ref.
|
87
|
+
# @return [Hash<String, Actor>] All committers from the given commit range
|
88
|
+
# @see #commits
|
89
|
+
def committers(range = self.class::DEFAULT_BRANCH)
|
90
|
+
range = parse_range range
|
91
|
+
commits(range) if @committers[range].nil?
|
92
|
+
@committers[range]
|
93
|
+
end
|
94
|
+
alias_method :collaborators, :committers
|
95
|
+
|
96
|
+
# This evaluates basic statistics about the files in a given commit range.
|
97
|
+
#
|
98
|
+
# @example
|
99
|
+
# repo.file_stats
|
100
|
+
# => {
|
101
|
+
# 'a_file.rb' => {
|
102
|
+
# :added_date => Tue Mar 29 16:13:47 +0200 2011,
|
103
|
+
# :deleted_date => Sun Jun 05 12:56:18 +0200 2011,
|
104
|
+
# :last_modified_date => Thu Apr 21 20:08:00 +0200 2011,
|
105
|
+
# :modifications => 9
|
106
|
+
# }
|
107
|
+
# }
|
108
|
+
# @param [String, Range] range The range of commits for which the file
|
109
|
+
# stats should be retrieved. This may be given as a string
|
110
|
+
# (`'master..development'`), a range (`'master'..'development'`) or
|
111
|
+
# as a single ref (`'master'`). A single ref name means all commits
|
112
|
+
# reachable from that ref.
|
113
|
+
# @return [Hash<String, Hash<Symbol, Object>>] Each file is returned as a
|
114
|
+
# key in this hash. The value of this key is another hash
|
115
|
+
# containing the stats for this file. Depending on the state of the
|
116
|
+
# file this includes `:added_date`, `:last_modified_date`,
|
117
|
+
# `:last_modified_date` and `'master..development'`.
|
118
|
+
# @see Commit#added_files
|
119
|
+
# @see Commit#deleted_files
|
120
|
+
# @see Commit#modified_files
|
121
|
+
def file_stats(range = self.class::DEFAULT_BRANCH)
|
122
|
+
support! :line_stats
|
123
|
+
|
124
|
+
stats = {}
|
125
|
+
commits(range).each do |commit|
|
126
|
+
commit.added_files.each do |file|
|
127
|
+
stats[file] = { :modifications => 0 } unless stats.key? file
|
128
|
+
stats[file][:added_date] = commit.authored_date
|
129
|
+
stats[file][:modifications] += 1
|
130
|
+
end
|
131
|
+
commit.modified_files.each do |file|
|
132
|
+
stats[file] = { :modifications => 0 } unless stats.key? file
|
133
|
+
stats[file][:last_modified_date] = commit.authored_date
|
134
|
+
stats[file][:modifications] += 1
|
135
|
+
end
|
136
|
+
commit.deleted_files.each do |file|
|
137
|
+
stats[file] = { :modifications => 0 } unless stats.key? file
|
138
|
+
stats[file][:deleted_date] = commit.authored_date
|
139
|
+
end
|
140
|
+
end
|
141
|
+
|
142
|
+
stats
|
143
|
+
end
|
144
|
+
|
145
|
+
# This evaluates the changed lines in each commit of the given commit
|
146
|
+
# range.
|
147
|
+
#
|
148
|
+
# For easier use, the values are stored in separate arrays where each
|
149
|
+
# number represents the number of changed (i.e. added or deleted) lines in
|
150
|
+
# one commit.
|
151
|
+
#
|
152
|
+
# @example
|
153
|
+
# repo.line_history
|
154
|
+
# => { :additions => [10, 5, 0], :deletions => [0, -2, -1] }
|
155
|
+
# @param [String, Range] range The range of commits for which the commit
|
156
|
+
# stats should be retrieved. This may be given as a string
|
157
|
+
# (`'master..development'`), a range (`'master'..'development'`) or
|
158
|
+
# as a single ref (`'master'`). A single ref name means all commits
|
159
|
+
# reachable from that ref.
|
160
|
+
# @return [Hash<Symbol, Array>] Added lines are returned in an `Array`
|
161
|
+
# assigned to key `:additions`, deleted lines are assigned to
|
162
|
+
# `:deletions`
|
163
|
+
# @see Commit#additions
|
164
|
+
# @see Commit#deletions
|
165
|
+
def line_history(range = self.class::DEFAULT_BRANCH)
|
166
|
+
support! :line_stats
|
167
|
+
|
168
|
+
history = { :additions => [], :deletions => [] }
|
169
|
+
commits(range).reverse.each do |commit|
|
170
|
+
history[:additions] << commit.additions
|
171
|
+
history[:deletions] << -commit.deletions
|
172
|
+
end
|
173
|
+
|
174
|
+
history
|
175
|
+
end
|
176
|
+
|
177
|
+
# Returns a list of authors with the biggest impact on the repository, i.e.
|
178
|
+
# changing the most code
|
179
|
+
#
|
180
|
+
# @param [String, Range] range The range of commits for which the authors
|
181
|
+
# should be retrieved. This may be given as a string
|
182
|
+
# (`'master..development'`), a range (`'master'..'development'`) or
|
183
|
+
# as a single ref (`'master'`). A single ref name means all commits
|
184
|
+
# reachable from that ref.
|
185
|
+
# @param [Fixnum] count The number of authors to return
|
186
|
+
# @raise [UnsupportedError] if the VCS does not support `:line_stats`
|
187
|
+
# @return [Array<Actor>] An array of the given number of the most
|
188
|
+
# significant authors in the given commit range
|
189
|
+
def significant_authors(range = self.class::DEFAULT_BRANCH, count = 3)
|
190
|
+
support! :line_stats
|
191
|
+
|
192
|
+
authors = authors(range).values.sort_by { |author| author.modifications }
|
193
|
+
count = [count, authors.size].min
|
194
|
+
authors[-count..-1].reverse
|
195
|
+
end
|
196
|
+
alias_method :significant_contributors, :significant_authors
|
197
|
+
|
198
|
+
# Returns a list of commits with the biggest impact on the repository, i.e.
|
199
|
+
# changing the most code
|
200
|
+
#
|
201
|
+
# @param [String, Range] range The range of commits for which the commits
|
202
|
+
# should be retrieved. This may be given as a string
|
203
|
+
# (`'master..development'`), a range (`'master'..'development'`) or
|
204
|
+
# as a single ref (`'master'`). A single ref name means all commits
|
205
|
+
# reachable from that ref.
|
206
|
+
# @param [Fixnum] count The number of commits to return
|
207
|
+
# @raise [UnsupportedError] if the VCS does not support `:line_stats`
|
208
|
+
# @return [Array<Actor>] An array of the given number of the most
|
209
|
+
# significant commits in the given commit range
|
210
|
+
def significant_commits(range = self.class::DEFAULT_BRANCH, count = 10)
|
211
|
+
support! :line_stats
|
212
|
+
|
213
|
+
commits = commits(range).sort_by { |commit| commit.modifications }
|
214
|
+
count = [count, commits.size].min
|
215
|
+
commits[-count..-1].reverse
|
216
|
+
end
|
217
|
+
|
218
|
+
# Returns a list of top contributors in the given commit range
|
219
|
+
#
|
220
|
+
# This will first have to load all authors (and i.e. commits) from the
|
221
|
+
# given commit range.
|
222
|
+
#
|
223
|
+
# @param [String, Range] range The range of commits for which the top
|
224
|
+
# contributors should be retrieved. This may be given as a string
|
225
|
+
# (`'master..development'`), a range (`'master'..'development'`) or
|
226
|
+
# as a single ref (`'master'`). A single ref name means all commits
|
227
|
+
# reachable from that ref.
|
228
|
+
# @param [Fixnum] count The number of contributors to return
|
229
|
+
# @return [Array<Actor>] An array of the given number of top contributors
|
230
|
+
# in the given commit range
|
231
|
+
# @see #authors
|
232
|
+
def top_authors(range = self.class::DEFAULT_BRANCH, count = 3)
|
233
|
+
authors = authors(range).values.sort_by { |author| author.commits.size }
|
234
|
+
count = [count, authors.size].min
|
235
|
+
authors[-count..-1].reverse
|
236
|
+
end
|
237
|
+
alias_method :top_contributors, :top_authors
|
238
|
+
|
239
|
+
private
|
240
|
+
|
241
|
+
# Loads all commits from the given commit range
|
242
|
+
#
|
243
|
+
# @abstract It has to be implemented by VCS specific subclasses
|
244
|
+
# @param [String, Range] range The range of commits for which the commits
|
245
|
+
# should be retrieved. This may be given as a string
|
246
|
+
# (`'master..development'`), a range (`'master'..'development'`) or
|
247
|
+
# as a single ref (`'master'`). A single ref name means all commits
|
248
|
+
# reachable from that ref.
|
249
|
+
# @return [Array<Commit>] All commits from the given commit range
|
250
|
+
def load_commits(range = self.class::DEFAULT_BRANCH)
|
251
|
+
raise NotImplementedError
|
252
|
+
end
|
253
|
+
|
254
|
+
# Parses a string with a single ref name into
|
255
|
+
#
|
256
|
+
# If a range is given it will be returned as-is.
|
257
|
+
#
|
258
|
+
# @param [String, Range] range The string that should be parsed for a range
|
259
|
+
# or an existing range
|
260
|
+
# @return [Range] The range parsed from a string or unchanged from the
|
261
|
+
# given parameter
|
262
|
+
def parse_range(range)
|
263
|
+
if range.is_a? Range
|
264
|
+
range
|
265
|
+
else
|
266
|
+
range = range.to_s.split '..'
|
267
|
+
((range.size == 1) ? '' : range.first)..range.last
|
268
|
+
end
|
269
|
+
end
|
270
|
+
|
271
|
+
end
|
272
|
+
|
273
|
+
end
|
data/lib/metior/vcs.rb
ADDED
@@ -0,0 +1,150 @@
|
|
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'
|
7
|
+
require 'metior/errors'
|
8
|
+
|
9
|
+
module Metior
|
10
|
+
|
11
|
+
# This hash will be dynamically filled with all available VCS types and the
|
12
|
+
# corresponding implementation modules
|
13
|
+
@@vcs_types = {}
|
14
|
+
|
15
|
+
# Returns the VCS implementation `Module` for a given symbolic VCS name
|
16
|
+
#
|
17
|
+
# @param [Symbol] type The symbolic type name of the VCS
|
18
|
+
# @return [VCS] The VCS for the given name
|
19
|
+
def self.vcs(type)
|
20
|
+
type = type.to_sym
|
21
|
+
unless @@vcs_types.key? type
|
22
|
+
raise 'No VCS registered for :%s' % type
|
23
|
+
end
|
24
|
+
@@vcs_types[type].init
|
25
|
+
end
|
26
|
+
|
27
|
+
# Returns a Hash with all available VCS types as keys and the implementation
|
28
|
+
# modules as values
|
29
|
+
#
|
30
|
+
# @return [Hash<Symbol, VCS>] All available VCS implementations and their
|
31
|
+
# corresponding names
|
32
|
+
def self.vcs_types
|
33
|
+
@@vcs_types
|
34
|
+
end
|
35
|
+
|
36
|
+
# This module provides functionality to automatically register new VCS
|
37
|
+
# implementations `Module`s
|
38
|
+
#
|
39
|
+
# @author Sebastian Staudt
|
40
|
+
module VCS
|
41
|
+
|
42
|
+
# This module provided class methods for VCS implementation `Module`s that
|
43
|
+
# implement smart auto-loading of dependencies and classes.
|
44
|
+
module ClassMethods
|
45
|
+
|
46
|
+
# Missing constants may indicate
|
47
|
+
#
|
48
|
+
# Trying to access either the `Actor`, `Commit` or `Repository` class
|
49
|
+
# in a VCS `Module` will trigger auto-loading first.
|
50
|
+
#
|
51
|
+
# @param [Symbol] The symbolic name of the missing constant
|
52
|
+
# @see #init
|
53
|
+
def const_missing(const)
|
54
|
+
init if [:Actor, :Commit, :Repository].include?(const)
|
55
|
+
super unless const_defined? const
|
56
|
+
const_get const
|
57
|
+
end
|
58
|
+
|
59
|
+
# This initializes the VCS's implementation `Module`
|
60
|
+
#
|
61
|
+
# First the corresponding Bundler group is loaded so all dependencies are
|
62
|
+
# met. Afterwards the `Actor`, `Commit` and `Repository` classes are
|
63
|
+
# required.
|
64
|
+
#
|
65
|
+
# @see Bundler.setup
|
66
|
+
def init
|
67
|
+
Bundler.setup self::NAME
|
68
|
+
|
69
|
+
path = self::NAME.to_s
|
70
|
+
require "metior/#{path}/actor"
|
71
|
+
require "metior/#{path}/commit"
|
72
|
+
require "metior/#{path}/repository"
|
73
|
+
|
74
|
+
self
|
75
|
+
end
|
76
|
+
|
77
|
+
# Marks one or more features as not supported by the VCSs (or its
|
78
|
+
# implementation)
|
79
|
+
#
|
80
|
+
# @param [Array<Symbol>] features The features that are not supported
|
81
|
+
# @see #supports?
|
82
|
+
def not_supported(*features)
|
83
|
+
features.each do |feature|
|
84
|
+
self.send(:class_variable_get, :@@features)[feature] = false
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
# Checks if a specific feature is supported by the VCS (or its
|
89
|
+
# implementation)
|
90
|
+
#
|
91
|
+
# @param [Symbol] feature The feature to check
|
92
|
+
# @return [true, false] `true` if the feature is supported
|
93
|
+
# @see #not_supported
|
94
|
+
# @see VCS#supports?
|
95
|
+
def supports?(feature)
|
96
|
+
self.send(:class_variable_get, :@@features)[feature] == true
|
97
|
+
end
|
98
|
+
|
99
|
+
end
|
100
|
+
|
101
|
+
# Including `VCS` will make a `Module` available as a supported VCS type in
|
102
|
+
# Metior
|
103
|
+
#
|
104
|
+
# @example This will automatically register `ExoticVCS` as `:exotic`
|
105
|
+
# module ExoticVCS
|
106
|
+
#
|
107
|
+
# NAME = :exotic
|
108
|
+
#
|
109
|
+
# include Metior::VCS
|
110
|
+
#
|
111
|
+
# end
|
112
|
+
#
|
113
|
+
# @param [Module] mod The `Module` that provides a Metior implementation
|
114
|
+
# for a specific VCS
|
115
|
+
# @raise [RuntimeError] if the VCS `Module` does not have the `NAME`
|
116
|
+
# constant defined prior to including `Metior::VCS`
|
117
|
+
# @see Metior.vcs_types
|
118
|
+
def self.included(mod)
|
119
|
+
mod.extend ClassMethods
|
120
|
+
mod.send :class_variable_set, :@@features, {
|
121
|
+
:file_stats => true,
|
122
|
+
:line_stats => true
|
123
|
+
}
|
124
|
+
|
125
|
+
raise "#{mod}::NAME is not set." unless mod.const_defined? :NAME
|
126
|
+
Metior.vcs_types[mod::NAME.to_sym] = mod
|
127
|
+
end
|
128
|
+
|
129
|
+
# Checks if a specific feature is supported by the VCS (or its
|
130
|
+
# implementation) and raises an error if the feature is not available
|
131
|
+
#
|
132
|
+
# @raise [UnsupportedError] if the feature is not supported by the VCS (or
|
133
|
+
# its implementation)
|
134
|
+
# @see #supports?
|
135
|
+
def support!(feature)
|
136
|
+
raise UnsupportedError unless supports? feature
|
137
|
+
end
|
138
|
+
|
139
|
+
# Checks if a specific feature is supported by the VCS (or its
|
140
|
+
# implementation)
|
141
|
+
#
|
142
|
+
# @return [true, false] `true` if the feature is supported
|
143
|
+
# @see ClassMethods#supports?
|
144
|
+
def supports?(feature)
|
145
|
+
singleton_class.included_modules.first.supports? feature
|
146
|
+
end
|
147
|
+
|
148
|
+
end
|
149
|
+
|
150
|
+
end
|