turbulence 0.0.9 → 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +4 -0
- data/Gemfile +0 -8
- data/Gemfile.lock +26 -23
- data/README.md +15 -4
- data/Rakefile +1 -0
- data/bin/bule +5 -4
- data/lib/turbulence/calculators/churn.rb +29 -14
- data/lib/turbulence/calculators/complexity.rb +3 -4
- data/lib/turbulence/checks_environment.rb +2 -5
- data/lib/turbulence/command_line_interface.rb +40 -11
- data/lib/turbulence/scatter_plot_generator.rb +47 -13
- data/lib/turbulence/scm/git.rb +17 -0
- data/lib/turbulence/scm/perforce.rb +90 -0
- data/lib/turbulence/version.rb +1 -1
- data/lib/turbulence.rb +27 -20
- data/spec/turbulence/calculators/churn_spec.rb +63 -11
- data/spec/turbulence/checks_environoment_spec.rb +11 -0
- data/spec/turbulence/command_line_interface_spec.rb +36 -6
- data/spec/turbulence/scatter_splot_generator_spec.rb +97 -0
- data/spec/turbulence/scm/git_spec.rb +29 -0
- data/spec/turbulence/scm/perforce_spec.rb +148 -0
- data/spec/turbulence/turbulence_spec.rb +15 -0
- data/turbulence.gemspec +4 -2
- data/win_rakefile_location_fix.rb +8 -0
- metadata +41 -35
- data/.DS_Store +0 -0
- data/cc.js +0 -1
- data/highcharts.js +0 -162
- data/jquery.min.js +0 -16
- data/turbulence.html +0 -81
data/.gitignore
CHANGED
data/Gemfile
CHANGED
data/Gemfile.lock
CHANGED
@@ -1,42 +1,45 @@
|
|
1
1
|
PATH
|
2
2
|
remote: .
|
3
3
|
specs:
|
4
|
-
turbulence (0.0.
|
4
|
+
turbulence (0.0.9)
|
5
|
+
ParseTree (~> 3.0.7)
|
5
6
|
flog (= 2.5.0)
|
6
|
-
json (
|
7
|
-
launchy (~> 0.
|
7
|
+
json (>= 1.4.6)
|
8
|
+
launchy (~> 2.0.0)
|
8
9
|
|
9
10
|
GEM
|
10
11
|
remote: http://rubygems.org/
|
11
12
|
specs:
|
12
|
-
|
13
|
-
|
13
|
+
ParseTree (3.0.8)
|
14
|
+
RubyInline (>= 3.7.0)
|
15
|
+
sexp_processor (>= 3.0.0)
|
16
|
+
RubyInline (3.11.0)
|
17
|
+
ZenTest (~> 4.3)
|
18
|
+
ZenTest (4.6.2)
|
19
|
+
addressable (2.2.6)
|
20
|
+
diff-lcs (1.1.3)
|
14
21
|
flog (2.5.0)
|
15
22
|
ruby_parser (~> 2.0)
|
16
23
|
sexp_processor (~> 3.0)
|
17
|
-
json (1.
|
18
|
-
launchy (0.
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
rspec-
|
24
|
-
|
25
|
-
|
26
|
-
rspec-core (2.5.1)
|
27
|
-
rspec-expectations (2.5.0)
|
24
|
+
json (1.6.1)
|
25
|
+
launchy (2.0.5)
|
26
|
+
addressable (~> 2.2.6)
|
27
|
+
rspec (2.6.0)
|
28
|
+
rspec-core (~> 2.6.0)
|
29
|
+
rspec-expectations (~> 2.6.0)
|
30
|
+
rspec-mocks (~> 2.6.0)
|
31
|
+
rspec-core (2.6.4)
|
32
|
+
rspec-expectations (2.6.0)
|
28
33
|
diff-lcs (~> 1.1.2)
|
29
|
-
rspec-mocks (2.
|
30
|
-
ruby_parser (2.
|
34
|
+
rspec-mocks (2.6.0)
|
35
|
+
ruby_parser (2.3.1)
|
31
36
|
sexp_processor (~> 3.0)
|
32
|
-
sexp_processor (3.0.
|
37
|
+
sexp_processor (3.0.7)
|
33
38
|
|
34
39
|
PLATFORMS
|
35
40
|
ruby
|
41
|
+
x86-mingw32
|
36
42
|
|
37
43
|
DEPENDENCIES
|
38
|
-
|
39
|
-
json (= 1.4.6)
|
40
|
-
launchy (~> 0.4.0)
|
41
|
-
rspec
|
44
|
+
rspec (~> 2.6.0)
|
42
45
|
turbulence!
|
data/README.md
CHANGED
@@ -1,17 +1,28 @@
|
|
1
1
|
Hopefully-meaningful Metrics
|
2
2
|
============================
|
3
3
|
|
4
|
-
|
5
|
-
http://www.stickyminds.com/sitewide.asp?Function=edetail&ObjectType=COL&ObjectId=16679&tth=DYN&tt=siteemail&iDyn=2
|
4
|
+
Based on Michael Feathers' [recent work](http://www.stickyminds.com/sitewide.asp?Function=edetail&ObjectType=COL&ObjectId=16679&tth=DYN&tt=siteemail&iDyn=2) in project churn and complexity.
|
6
5
|
|
6
|
+
Installation
|
7
|
+
------------
|
8
|
+
|
9
|
+
$ gem install turbulence
|
7
10
|
|
8
11
|
Usage
|
9
12
|
-----
|
13
|
+
In your project directory, run:
|
14
|
+
|
15
|
+
$ bule
|
16
|
+
|
17
|
+
and it will generate (and open) turbulence/turbulence.html
|
10
18
|
|
11
|
-
|
19
|
+
Supported SCM systems
|
20
|
+
---------------------
|
21
|
+
Currently, bule defaults to using git. If you are using Perforce, call it like so:
|
12
22
|
|
13
|
-
|
23
|
+
$ bule --scm p4
|
14
24
|
|
25
|
+
You need to have an environment variable P4CLIENT set to the name of your client workspace.
|
15
26
|
|
16
27
|
WARNING
|
17
28
|
-------
|
data/Rakefile
CHANGED
data/bin/bule
CHANGED
@@ -2,12 +2,13 @@
|
|
2
2
|
require 'turbulence'
|
3
3
|
require 'turbulence/checks_environment'
|
4
4
|
|
5
|
-
|
6
|
-
|
7
|
-
|
5
|
+
cli = Turbulence::CommandLineInterface.new(ARGV)
|
6
|
+
|
7
|
+
unless Turbulence::ChecksEnvironment.scm_repo?(Dir.pwd)
|
8
|
+
STDERR.puts "Turbulence could not calculate metrics, as we could not find a repository in the current directory."
|
9
|
+
STDERR.puts "Please run bule from inside a repository."
|
8
10
|
exit
|
9
11
|
end
|
10
12
|
|
11
|
-
cli = Turbulence::CommandLineInterface.new(Dir.pwd)
|
12
13
|
cli.generate_bundle
|
13
14
|
cli.open_bundle
|
@@ -1,38 +1,53 @@
|
|
1
1
|
class Turbulence
|
2
2
|
module Calculators
|
3
3
|
class Churn
|
4
|
+
RUBY_FILE_EXTENSION = ".rb"
|
5
|
+
|
4
6
|
class << self
|
7
|
+
attr_accessor :scm, :compute_mean, :commit_range
|
8
|
+
|
5
9
|
def for_these_files(files)
|
6
|
-
changes_by_ruby_file.
|
10
|
+
changes_by_ruby_file.each do |filename, count|
|
7
11
|
yield filename, count if files.include?(filename)
|
8
12
|
end
|
9
13
|
end
|
10
14
|
|
11
15
|
def changes_by_ruby_file
|
12
|
-
|
13
|
-
|
16
|
+
ruby_files_changed_in_scm.group_by(&:first).map do |filename, stats|
|
17
|
+
churn_for_file(filename,stats)
|
14
18
|
end
|
15
19
|
end
|
16
20
|
|
17
|
-
def
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
+
def churn_for_file(filename,stats)
|
22
|
+
churn = stats[0..-2].map(&:last).inject(0){|running_total, changes| running_total + changes}
|
23
|
+
churn = calculate_mean_of_churn(churn, stats.size - 1) if compute_mean
|
24
|
+
[filename, churn]
|
25
|
+
end
|
26
|
+
|
27
|
+
def calculate_mean_of_churn(churn, sample_size)
|
28
|
+
return churn if sample_size < 1
|
29
|
+
churn /= sample_size
|
30
|
+
end
|
31
|
+
|
32
|
+
def ruby_files_changed_in_scm
|
33
|
+
counted_line_changes_by_file_by_commit.select do |filename, _|
|
34
|
+
filename.end_with?(RUBY_FILE_EXTENSION) && File.exist?(filename)
|
21
35
|
end
|
22
36
|
end
|
23
37
|
|
24
|
-
def
|
25
|
-
|
26
|
-
|
38
|
+
def counted_line_changes_by_file_by_commit
|
39
|
+
scm_log_file_lines.map do |line|
|
40
|
+
adds, deletes, filename = line.split(/\t/)
|
41
|
+
[filename, adds.to_i + deletes.to_i]
|
27
42
|
end
|
28
43
|
end
|
29
44
|
|
30
|
-
def
|
31
|
-
|
45
|
+
def scm_log_file_lines
|
46
|
+
scm_log_command.each_line.reject{|line| line == "\n"}.map(&:chomp)
|
32
47
|
end
|
33
48
|
|
34
|
-
def
|
35
|
-
|
49
|
+
def scm_log_command
|
50
|
+
scm.log_command(commit_range)
|
36
51
|
end
|
37
52
|
end
|
38
53
|
end
|
@@ -5,7 +5,7 @@ class Turbulence
|
|
5
5
|
class Complexity
|
6
6
|
class << self
|
7
7
|
def flogger
|
8
|
-
@flogger ||= Flog.new
|
8
|
+
@flogger ||= Flog.new(:continue => true)
|
9
9
|
end
|
10
10
|
def for_these_files(files)
|
11
11
|
files.each do |filename|
|
@@ -18,14 +18,13 @@ class Turbulence
|
|
18
18
|
reporter = Reporter.new
|
19
19
|
flogger.report(reporter)
|
20
20
|
reporter.score
|
21
|
-
rescue SyntaxError, Racc::ParseError
|
22
|
-
STDERR.puts "\nError flogging: #{filename}\n"
|
23
21
|
end
|
24
22
|
end
|
25
23
|
|
26
24
|
class Reporter < ::StringIO
|
25
|
+
SCORE_LINE_DETECTOR = /^\s+([^:]+).*flog total$/
|
27
26
|
def score
|
28
|
-
Float(string.scan(
|
27
|
+
Float(string.scan(SCORE_LINE_DETECTOR).flatten.first)
|
29
28
|
end
|
30
29
|
end
|
31
30
|
end
|
@@ -1,11 +1,8 @@
|
|
1
|
-
require 'open3'
|
2
1
|
class Turbulence
|
3
2
|
class ChecksEnvironment
|
4
3
|
class << self
|
5
|
-
def
|
6
|
-
|
7
|
-
return !(err.read =~ /Not a git repository/)
|
8
|
-
end
|
4
|
+
def scm_repo?(directory)
|
5
|
+
Turbulence::Calculators::Churn.scm.is_repo?(directory)
|
9
6
|
end
|
10
7
|
end
|
11
8
|
end
|
@@ -1,23 +1,51 @@
|
|
1
1
|
require 'fileutils'
|
2
2
|
require 'launchy'
|
3
|
+
require 'optparse'
|
4
|
+
require 'turbulence/scm/git'
|
5
|
+
require 'turbulence/scm/perforce'
|
3
6
|
|
4
7
|
class Turbulence
|
5
8
|
class CommandLineInterface
|
6
|
-
|
7
|
-
|
9
|
+
TURBULENCE_TEMPLATE_PATH = File.join(File.expand_path(File.dirname(__FILE__)), "..", "..", "template")
|
10
|
+
TEMPLATE_FILES = ['turbulence.html', 'highcharts.js', 'jquery.min.js'].map { |filename|
|
11
|
+
File.join(TURBULENCE_TEMPLATE_PATH, filename)
|
12
|
+
}
|
13
|
+
|
14
|
+
attr_reader :exclusion_pattern
|
8
15
|
attr_reader :directory
|
9
|
-
def initialize(
|
10
|
-
|
11
|
-
|
16
|
+
def initialize(argv)
|
17
|
+
Turbulence::Calculators::Churn.scm = Scm::Git
|
18
|
+
OptionParser.new do |opts|
|
19
|
+
opts.banner = "Usage: bule [options] [dir]"
|
20
|
+
|
21
|
+
opts.on('--scm p4|git', String, 'scm to use (default: git)') do |s|
|
22
|
+
case s
|
23
|
+
when "git", "", nil
|
24
|
+
when "p4"
|
25
|
+
Turbulence::Calculators::Churn.scm = Scm::Perforce
|
26
|
+
end
|
27
|
+
end
|
28
|
+
opts.on('--churn-range since..until', String, 'commit range to compute file churn') do |s|
|
29
|
+
Turbulence::Calculators::Churn.commit_range = s
|
30
|
+
end
|
31
|
+
opts.on('--churn-mean', 'calculate mean churn instead of cummulative') do
|
32
|
+
Turbulence::Calculators::Churn.compute_mean = true
|
33
|
+
end
|
34
|
+
opts.on('--exclude pattern', String, 'exclude files matching pattern') do |pattern|
|
35
|
+
@exclusion_pattern = pattern
|
36
|
+
end
|
12
37
|
|
13
|
-
|
14
|
-
|
38
|
+
opts.on_tail("-h", "--help", "Show this message") do
|
39
|
+
puts opts
|
40
|
+
exit
|
41
|
+
end
|
42
|
+
end.parse!(argv)
|
43
|
+
|
44
|
+
@directory = argv.first || Dir.pwd
|
15
45
|
end
|
16
46
|
|
17
47
|
def copy_templates_into(directory)
|
18
|
-
|
19
|
-
FileUtils.cp path_to_template(filename), directory
|
20
|
-
end
|
48
|
+
FileUtils.cp TEMPLATE_FILES, directory
|
21
49
|
end
|
22
50
|
private :copy_templates_into
|
23
51
|
|
@@ -26,7 +54,8 @@ class Turbulence
|
|
26
54
|
Dir.chdir("turbulence") do
|
27
55
|
copy_templates_into(Dir.pwd)
|
28
56
|
File.open("cc.js", "w") do |f|
|
29
|
-
|
57
|
+
turb = Turbulence.new(directory,STDOUT, @exclusion_pattern)
|
58
|
+
f.write Turbulence::ScatterPlotGenerator.from(turb.metrics).to_js
|
30
59
|
end
|
31
60
|
end
|
32
61
|
end
|
@@ -1,31 +1,65 @@
|
|
1
1
|
require 'json'
|
2
|
+
|
2
3
|
class Turbulence
|
4
|
+
class FileNameMangler
|
5
|
+
def initialize
|
6
|
+
@current_id = 0
|
7
|
+
@segment_map = { "" => "", "app" => "app", "controllers" => "controllers", "helpers" => "helpers", "lib" => "lib" }
|
8
|
+
end
|
9
|
+
|
10
|
+
def transform(segment)
|
11
|
+
@segment_map[segment] ||= (@current_id += 1)
|
12
|
+
end
|
13
|
+
|
14
|
+
def mangle_name(filename)
|
15
|
+
filename.split('/').map {|seg|transform(seg)}.join('/') + ".rb"
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
3
19
|
class ScatterPlotGenerator
|
4
20
|
def self.from(metrics_hash)
|
5
21
|
new(metrics_hash)
|
6
22
|
end
|
7
|
-
attr_reader :metrics_hash
|
8
|
-
def initialize(metrics_hash)
|
23
|
+
attr_reader :metrics_hash, :x_metric, :y_metric
|
24
|
+
def initialize(metrics_hash, x_metric = Turbulence::Calculators::Churn, y_metric = Turbulence::Calculators::Complexity)
|
25
|
+
@x_metric = x_metric
|
26
|
+
@y_metric = y_metric
|
9
27
|
@metrics_hash = metrics_hash
|
10
28
|
end
|
11
29
|
|
30
|
+
def mangle
|
31
|
+
mangler = FileNameMangler.new
|
32
|
+
mangled = {}
|
33
|
+
metrics_hash.each_pair { |filename, metrics| mangled[mangler.mangle_name(filename)] = metrics}
|
34
|
+
@metrics_hash = mangled
|
35
|
+
end
|
36
|
+
|
12
37
|
def to_js
|
13
|
-
|
38
|
+
clean_metrics_from_missing_data
|
39
|
+
directory_series = {}
|
40
|
+
grouped_by_directory.each_pair do |directory, metrics_hash|
|
41
|
+
directory_series[directory] = file_metrics_for_directory(metrics_hash) end
|
42
|
+
|
43
|
+
"var directorySeries = #{directory_series.to_json};"
|
44
|
+
end
|
45
|
+
|
46
|
+
def clean_metrics_from_missing_data
|
47
|
+
metrics_hash.reject! do |filename, metrics|
|
48
|
+
metrics[x_metric].nil? || metrics[y_metric].nil?
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
def grouped_by_directory
|
53
|
+
metrics_hash.group_by do |filename, _|
|
14
54
|
directories = File.dirname(filename).split("/")
|
15
55
|
directories[0..1].join("/")
|
16
56
|
end
|
57
|
+
end
|
17
58
|
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
{:filename => filename, :x => metrics[Turbulence::Calculators::Churn], :y => metrics[Turbulence::Calculators::Complexity]}
|
22
|
-
end.reject do |metrics|
|
23
|
-
metrics[:x].nil? || metrics[:y].nil?
|
24
|
-
end
|
25
|
-
directory_series[directory] = data_in_json_format
|
59
|
+
def file_metrics_for_directory(metrics_hash)
|
60
|
+
metrics_hash.map do |filename, metrics|
|
61
|
+
{:filename => filename, :x => metrics[x_metric], :y => metrics[y_metric]}
|
26
62
|
end
|
27
|
-
|
28
|
-
"var directorySeries = #{directory_series.to_json};"
|
29
63
|
end
|
30
64
|
end
|
31
65
|
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
class Turbulence
|
2
|
+
module Scm
|
3
|
+
class Git
|
4
|
+
class << self
|
5
|
+
def log_command(commit_range = "")
|
6
|
+
`git log --all -M -C --numstat --format="%n" #{commit_range}`
|
7
|
+
end
|
8
|
+
|
9
|
+
def is_repo?(directory)
|
10
|
+
FileUtils.cd(directory) {
|
11
|
+
return !(`git status 2>&1` =~ /Not a git repository/)
|
12
|
+
}
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,90 @@
|
|
1
|
+
require 'fileutils'
|
2
|
+
require 'pathname'
|
3
|
+
|
4
|
+
class Turbulence
|
5
|
+
module Scm
|
6
|
+
class Perforce
|
7
|
+
class << self
|
8
|
+
def log_command(commit_range = "")
|
9
|
+
full_log = ""
|
10
|
+
changes.each do |cn|
|
11
|
+
files_per_change(cn).each do |file|
|
12
|
+
full_log << transform_for_output(file)
|
13
|
+
end
|
14
|
+
end
|
15
|
+
return full_log
|
16
|
+
end
|
17
|
+
|
18
|
+
def is_repo?(directory)
|
19
|
+
p4client = ENV['P4CLIENT']
|
20
|
+
return !((p4client.nil? or p4client.empty?) and not self.has_p4?)
|
21
|
+
end
|
22
|
+
|
23
|
+
def has_p4?
|
24
|
+
ENV['PATH'].split(File::PATH_SEPARATOR).any? do |directory|
|
25
|
+
File.executable?(File.join(directory, 'p4'))
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
def changes(commit_range = "")
|
30
|
+
p4_list_changes.each_line.map do |change|
|
31
|
+
change.match(/Change (\d+)/)[1]
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
def depot_to_local(depot_file)
|
36
|
+
abs_path = extract_clientfile_from_fstat_of(depot_file)
|
37
|
+
Pathname.new(abs_path).relative_path_from(Pathname.new(FileUtils.pwd)).to_s
|
38
|
+
end
|
39
|
+
|
40
|
+
def extract_clientfile_from_fstat_of(depot_file)
|
41
|
+
p4_fstat(depot_file).each_line.select {
|
42
|
+
|line| line =~ /clientFile/
|
43
|
+
}[0].split(" ")[2].tr("\\","/")
|
44
|
+
end
|
45
|
+
|
46
|
+
def files_per_change(change)
|
47
|
+
describe_output = p4_describe_change(change).split("\n")
|
48
|
+
map = []
|
49
|
+
describe_output.each_index do |index|
|
50
|
+
if describe_output[index].start_with?("====")
|
51
|
+
fn = filename_from_describe(describe_output, index)
|
52
|
+
churn = sum_of_changes(describe_output[index .. index + 4].join("\n"))
|
53
|
+
map << [churn,fn]
|
54
|
+
end
|
55
|
+
end
|
56
|
+
return map
|
57
|
+
end
|
58
|
+
|
59
|
+
def filename_from_describe(output,index)
|
60
|
+
depot_to_local(output[index].match(/==== (\/\/.*)#\d+/)[1])
|
61
|
+
end
|
62
|
+
|
63
|
+
def transform_for_output(arr)
|
64
|
+
"#{arr[0]}\t0\t#{arr[1]}\n"
|
65
|
+
end
|
66
|
+
|
67
|
+
def sum_of_changes(p4_describe_output)
|
68
|
+
churn = 0
|
69
|
+
p4_describe_output.each_line do |line|
|
70
|
+
next unless line =~ /(add|deleted|changed) .* (\d+) lines/
|
71
|
+
churn += line.match(/(\d+) lines/)[1].to_i
|
72
|
+
end
|
73
|
+
return churn
|
74
|
+
end
|
75
|
+
|
76
|
+
def p4_list_changes(commit_range = "")
|
77
|
+
`p4 changes -s submitted ...#{commit_range}`
|
78
|
+
end
|
79
|
+
|
80
|
+
def p4_fstat(depot_file)
|
81
|
+
`p4 fstat #{depot_file}`
|
82
|
+
end
|
83
|
+
|
84
|
+
def p4_describe_change(change)
|
85
|
+
`p4 describe -ds #{change}`
|
86
|
+
end
|
87
|
+
end
|
88
|
+
end
|
89
|
+
end
|
90
|
+
end
|
data/lib/turbulence/version.rb
CHANGED
data/lib/turbulence.rb
CHANGED
@@ -1,43 +1,43 @@
|
|
1
1
|
require 'turbulence/scatter_plot_generator'
|
2
2
|
require 'turbulence/command_line_interface'
|
3
|
+
require 'turbulence/checks_environment'
|
3
4
|
require 'turbulence/calculators/churn'
|
4
5
|
require 'turbulence/calculators/complexity'
|
5
6
|
|
6
7
|
class Turbulence
|
7
|
-
|
8
|
+
CODE_DIRECTORIES = ["app/models", "app/controllers", "app/helpers", "lib"]
|
9
|
+
CALCULATORS = [Turbulence::Calculators::Complexity, Turbulence::Calculators::Churn]
|
10
|
+
|
11
|
+
attr_reader :exclusion_pattern
|
8
12
|
attr_reader :metrics
|
9
|
-
def initialize(
|
10
|
-
@
|
13
|
+
def initialize(directory, output = nil, exclusion_pattern = nil)
|
14
|
+
@output = output
|
11
15
|
@metrics = {}
|
12
|
-
|
13
|
-
|
14
|
-
|
16
|
+
@exclusion_pattern = exclusion_pattern
|
17
|
+
Dir.chdir(directory) do
|
18
|
+
CALCULATORS.each(&method(:calculate_metrics_with))
|
15
19
|
end
|
16
20
|
end
|
17
21
|
|
18
22
|
def files_of_interest
|
19
|
-
|
20
|
-
@ruby_files ||= Dir[*
|
21
|
-
end
|
22
|
-
|
23
|
-
def complexity
|
24
|
-
calculate_metrics Turbulence::Calculators::Complexity
|
25
|
-
end
|
26
|
-
|
27
|
-
def churn
|
28
|
-
calculate_metrics Turbulence::Calculators::Churn
|
23
|
+
file_list = CODE_DIRECTORIES.map{|base_dir| "#{base_dir}/**/*\.rb"}
|
24
|
+
@ruby_files ||= exclude_files(Dir[*file_list])
|
29
25
|
end
|
30
26
|
|
31
|
-
def
|
32
|
-
|
27
|
+
def calculate_metrics_with(calculator)
|
28
|
+
report "calculating metric: #{calculator}\n"
|
33
29
|
calculator.for_these_files(files_of_interest) do |filename, score|
|
30
|
+
report "."
|
34
31
|
set_file_metric(filename, calculator, score)
|
35
32
|
end
|
36
|
-
|
33
|
+
report "\n"
|
34
|
+
end
|
35
|
+
|
36
|
+
def report(this)
|
37
|
+
@output.print this unless @output.nil?
|
37
38
|
end
|
38
39
|
|
39
40
|
def set_file_metric(filename, metric, value)
|
40
|
-
print "."
|
41
41
|
metrics_for(filename)[metric] = value
|
42
42
|
end
|
43
43
|
|
@@ -45,4 +45,11 @@ class Turbulence
|
|
45
45
|
@metrics[filename] ||= {}
|
46
46
|
end
|
47
47
|
|
48
|
+
private
|
49
|
+
def exclude_files(files)
|
50
|
+
if not @exclusion_pattern.nil?
|
51
|
+
files = files.reject { |f| f =~ Regexp.new(@exclusion_pattern) }
|
52
|
+
end
|
53
|
+
files
|
54
|
+
end
|
48
55
|
end
|