turbulence 0.0.3 → 0.0.4

Sign up to get free protection for your applications and to get access to all the features.
data/Gemfile CHANGED
@@ -6,3 +6,7 @@ gemspec
6
6
  gem 'flog'
7
7
  gem 'json'
8
8
  gem 'launchy', '~> 0.4.0'
9
+
10
+ group :development, :test do
11
+ gem 'rspec'
12
+ end
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- turbulence (0.0.2)
4
+ turbulence (0.0.3)
5
5
  flog (= 2.5.0)
6
6
  json (= 1.4.6)
7
7
  launchy (~> 0.4.0)
@@ -10,6 +10,7 @@ GEM
10
10
  remote: http://rubygems.org/
11
11
  specs:
12
12
  configuration (1.2.0)
13
+ diff-lcs (1.1.2)
13
14
  flog (2.5.0)
14
15
  ruby_parser (~> 2.0)
15
16
  sexp_processor (~> 3.0)
@@ -18,6 +19,14 @@ GEM
18
19
  configuration (>= 0.0.5)
19
20
  rake (>= 0.8.1)
20
21
  rake (0.8.7)
22
+ rspec (2.5.0)
23
+ rspec-core (~> 2.5.0)
24
+ rspec-expectations (~> 2.5.0)
25
+ rspec-mocks (~> 2.5.0)
26
+ rspec-core (2.5.1)
27
+ rspec-expectations (2.5.0)
28
+ diff-lcs (~> 1.1.2)
29
+ rspec-mocks (2.5.0)
21
30
  ruby_parser (2.0.6)
22
31
  sexp_processor (~> 3.0)
23
32
  sexp_processor (3.0.5)
@@ -29,4 +38,5 @@ DEPENDENCIES
29
38
  flog
30
39
  json
31
40
  launchy (~> 0.4.0)
41
+ rspec
32
42
  turbulence!
data/bin/bule CHANGED
@@ -1,32 +1,6 @@
1
1
  #!/usr/bin/env ruby
2
2
  require 'turbulence'
3
- require 'turbulence/scatter_plot_generator'
4
- require 'fileutils'
5
- require 'launchy'
6
- TURBULENCE_PATH = File.join(File.absolute_path(File.dirname(__FILE__)), "..")
7
3
 
8
- def path_to_template(filename)
9
- File.join(TURBULENCE_PATH, "template", filename)
10
- end
11
-
12
- def copy_templates_into(directory)
13
- FileUtils.cp path_to_template('turbulence.html'), directory
14
- FileUtils.cp path_to_template('highcharts.js'), directory
15
- FileUtils.cp path_to_template('jquery.min.js'), directory
16
- end
17
-
18
- def generate_bundle_for(git_repo)
19
- FileUtils.mkdir_p("turbulence")
20
- Dir.chdir("turbulence") do
21
- copy_templates_into(Dir.pwd)
22
- File.open("cc.js", "w") do |f|
23
- f.write Turbulence::ScatterPlotGenerator.from(Turbulence.new(git_repo).metrics)
24
- end
25
- end
26
- end
27
-
28
- def open_bundle(directory)
29
- Launchy.open("file://#{directory}/turbulence/turbulence.html")
30
- end
31
- generate_bundle_for Dir.pwd
32
- open_bundle Dir.pwd
4
+ cli = Turbulence::CommandLineInterface.new(Dir.pwd)
5
+ cli.generate_bundle
6
+ cli.open_bundle
@@ -1,13 +1,9 @@
1
- require 'flog'
2
- require 'stringio'
1
+ require 'turbulence/scatter_plot_generator'
2
+ require 'turbulence/command_line_interface'
3
+ require 'turbulence/calculators/churn'
4
+ require 'turbulence/calculators/complexity'
3
5
 
4
6
  class Turbulence
5
- class Reporter < StringIO
6
- def average
7
- Float(string.scan(/^\s+([^:]+).*total$/).flatten.first)
8
- end
9
- end
10
-
11
7
  attr_reader :dir
12
8
  attr_reader :metrics
13
9
  def initialize(dir)
@@ -19,52 +15,34 @@ class Turbulence
19
15
  end
20
16
  end
21
17
 
22
- def ruby_files
18
+ def files_of_interest
23
19
  files = ["app/models", "app/controllers", "app/helpers", "lib"].map{|base_dir| "#{base_dir}/**/*\.rb"}
24
20
  @ruby_files ||= Dir[*files]
25
21
  end
26
22
 
27
- def churn
28
- files = changes_by_ruby_file.select { |_, filename| ruby_files.include?(filename) }
29
- files.each do |count, filename|
30
- print "."
31
- metrics_for(filename)[:churn] = Integer(count)
32
- end
23
+ def complexity
24
+ calculate_metrics Turbulence::Calculators::Complexity
33
25
  end
34
26
 
35
- def complexity
36
- flogger = Flog.new
37
- ruby_files.each do |filename|
38
- print "."
27
+ def churn
28
+ calculate_metrics Turbulence::Calculators::Churn
29
+ end
39
30
 
40
- begin
41
- flogger.flog filename
42
- reporter = Reporter.new
43
- flogger.report(reporter)
44
- metrics_for(filename)[:complexity] = reporter.average
45
- rescue SyntaxError, Racc::ParseError => e
46
- puts "\nError flogging: #{filename}\n"
47
- end
31
+ def calculate_metrics(calculator)
32
+ puts "calculating metric: #{calculator}"
33
+ calculator.for_these_files(files_of_interest) do |filename, score|
34
+ set_file_metric(filename, calculator, score)
48
35
  end
36
+ puts "\n"
37
+ end
38
+
39
+ def set_file_metric(filename, metric, value)
40
+ print "."
41
+ metrics_for(filename)[metric] = value
49
42
  end
50
43
 
51
44
  def metrics_for(filename)
52
45
  @metrics[filename] ||= {}
53
46
  end
54
47
 
55
- private
56
- def changes_by_ruby_file
57
- line_changes_by_file.select do |count, filename|
58
- filename =~ /\.rb$/ && File.exist?(filename)
59
- end
60
- end
61
-
62
- def line_changes_by_file
63
- `git log --all -M -C --numstat --format="%n"`.each_line.reject{|line| line =~ /^\n$/}.map do |line|
64
- adds, deletes, filename = line.chomp.split(/\t/)
65
- [filename, adds.to_i + deletes.to_i]
66
- end.group_by(&:first).map do |filename, stats|
67
- [stats.map(&:last).tap{|list| list.pop}.inject(0){|n, i| n + i}, filename]
68
- end
69
- end
70
48
  end
@@ -0,0 +1,40 @@
1
+ class Turbulence
2
+ module Calculators
3
+ class Churn
4
+ class << self
5
+ def for_these_files(files)
6
+ changes_by_ruby_file.select do |count, filename|
7
+ yield filename, count if files.include?(filename)
8
+ end
9
+ end
10
+
11
+ def changes_by_ruby_file
12
+ ruby_files_changed_in_git.group_by(&:first).map do |filename, stats|
13
+ [stats.map(&:last).tap{|list| list.pop}.inject(0){|n, i| n + i}, filename]
14
+ end
15
+ end
16
+
17
+ def counted_line_changes_by_file_by_commit
18
+ git_log_file_lines.map do |line|
19
+ adds, deletes, filename = line.split(/\t/)
20
+ [filename, adds.to_i + deletes.to_i]
21
+ end
22
+ end
23
+
24
+ def ruby_files_changed_in_git
25
+ counted_line_changes_by_file_by_commit.select do |filename, count|
26
+ filename =~ /\.rb$/ && File.exist?(filename)
27
+ end
28
+ end
29
+
30
+ def git_log_file_lines
31
+ git_log_command.each_line.reject{|line| line =~ /^\n$/}.map(&:chomp)
32
+ end
33
+
34
+ def git_log_command
35
+ `git log --all -M -C --numstat --format="%n"`
36
+ end
37
+ end
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,33 @@
1
+ require 'stringio'
2
+ require 'flog'
3
+ class Turbulence
4
+ module Calculators
5
+ class Complexity
6
+ class << self
7
+ def flogger
8
+ @flogger ||= Flog.new
9
+ end
10
+ def for_these_files(files)
11
+ files.each do |filename|
12
+ yield filename, score_for_file(filename)
13
+ end
14
+ end
15
+
16
+ def score_for_file(filename)
17
+ flogger.flog filename
18
+ reporter = Reporter.new
19
+ flogger.report(reporter)
20
+ reporter.score
21
+ rescue SyntaxError, Racc::ParseError
22
+ STDERR.puts "\nError flogging: #{filename}\n"
23
+ end
24
+ end
25
+
26
+ class Reporter < ::StringIO
27
+ def score
28
+ Float(string.scan(/^\s+([^:]+).*total$/).flatten.first)
29
+ end
30
+ end
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,37 @@
1
+ require 'fileutils'
2
+ require 'launchy'
3
+
4
+ class Turbulence
5
+ class CommandLineInterface
6
+ TURBULENCE_PATH = File.join(File.absolute_path(File.dirname(__FILE__)), "..", "..")
7
+
8
+ attr_reader :directory
9
+ def initialize(directory)
10
+ @directory = directory
11
+ end
12
+
13
+ def path_to_template(filename)
14
+ File.join(TURBULENCE_PATH, "template", filename)
15
+ end
16
+
17
+ def copy_templates_into(directory)
18
+ ['turbulence.html', 'highcharts.js', 'jquery.min.js'].each do |filename|
19
+ FileUtils.cp path_to_template(filename), directory
20
+ end
21
+ end
22
+
23
+ def generate_bundle
24
+ FileUtils.mkdir_p("turbulence")
25
+ Dir.chdir("turbulence") do
26
+ copy_templates_into(Dir.pwd)
27
+ File.open("cc.js", "w") do |f|
28
+ f.write Turbulence::ScatterPlotGenerator.from(Turbulence.new(directory).metrics).to_js
29
+ end
30
+ end
31
+ end
32
+
33
+ def open_bundle
34
+ Launchy.open("file://#{directory}/turbulence/turbulence.html")
35
+ end
36
+ end
37
+ end
@@ -2,14 +2,14 @@ require 'json'
2
2
  class Turbulence
3
3
  class ScatterPlotGenerator
4
4
  def self.from(metrics_hash)
5
+ new(metrics_hash)
6
+ end
7
+ attr_reader :metrics_hash
8
+ def initialize(metrics_hash)
9
+ @metrics_hash = metrics_hash
10
+ end
5
11
 
6
- data_in_json_format = metrics_hash.map do |filename, metrics|
7
- {:filename => filename, :x => metrics[:churn], :y => metrics[:complexity]}
8
- end.reject do |metrics|
9
- metrics[:x].nil? || metrics[:y].nil?
10
- end.to_json
11
- series = ["var turbulenceGraphData = #{data_in_json_format};"]
12
-
12
+ def to_js
13
13
  grouped_by_directory = metrics_hash.group_by do |filename, _|
14
14
  directories = File.dirname(filename).split("/")
15
15
  directories[0..1].join("/")
@@ -18,16 +18,14 @@ class Turbulence
18
18
  directory_series = {}
19
19
  grouped_by_directory.each_pair do |directory, metrics_hash|
20
20
  data_in_json_format = metrics_hash.map do |filename, metrics|
21
- {:filename => filename, :x => metrics[:churn], :y => metrics[:complexity]}
21
+ {:filename => filename, :x => metrics[Turbulence::Calculators::Churn], :y => metrics[Turbulence::Calculators::Complexity]}
22
22
  end.reject do |metrics|
23
- metrics[:x].nil? || metrics[:y].nil?
23
+ metrics[:x].nil? || metrics[:y].nil?
24
24
  end
25
25
  directory_series[directory] = data_in_json_format
26
26
  end
27
27
 
28
- series << "var directorySeries = #{directory_series.to_json};"
29
-
30
- series.join("\n")
28
+ "var directorySeries = #{directory_series.to_json};"
31
29
  end
32
30
  end
33
31
  end
@@ -1,3 +1,3 @@
1
1
  class Turbulence
2
- VERSION = "0.0.3"
2
+ VERSION = "0.0.4"
3
3
  end
@@ -0,0 +1,103 @@
1
+ require 'turbulence/calculators/churn'
2
+
3
+ describe Turbulence::Calculators::Churn do
4
+ let(:calculator) { Turbulence::Calculators::Churn }
5
+ before do
6
+ calculator.stub(:git_log_command) { "" }
7
+ end
8
+
9
+ describe "::for_these_files" do
10
+ it "yields up the filename and score for each file" do
11
+ files = ["lib/corey.rb", "lib/chad.rb"]
12
+ calculator.stub(:changes_by_ruby_file) {
13
+ [
14
+ [5, "lib/corey.rb"],
15
+ [10, "lib/chad.rb"]
16
+ ]
17
+ }
18
+ yielded_files = []
19
+ calculator.for_these_files(files) do |filename, score|
20
+ yielded_files << [filename, score]
21
+ end
22
+ yielded_files.should =~ [["lib/corey.rb", 5],
23
+ ["lib/chad.rb",10]]
24
+ end
25
+
26
+ it "filters the results by the passed-in files" do
27
+ files = ["lib/corey.rb"]
28
+ calculator.stub(:changes_by_ruby_file) {
29
+ [
30
+ [5, "lib/corey.rb"],
31
+ [10, "lib/chad.rb"]
32
+ ]
33
+ }
34
+ yielded_files = []
35
+ calculator.for_these_files(files) do |filename, score|
36
+ yielded_files << [filename, score]
37
+ end
38
+ yielded_files.should =~ [["lib/corey.rb", 5]]
39
+ end
40
+ end
41
+
42
+ describe "::git_log_file_lines" do
43
+ it "returns just the file lines" do
44
+ calculator.stub(:git_log_command) do
45
+ "\n\n\n\n10\t6\tlib/turbulence.rb\n\n\n\n17\t2\tlib/eddies.rb\n"
46
+ end
47
+
48
+ calculator.git_log_file_lines.should =~ [
49
+ "10\t6\tlib/turbulence.rb",
50
+ "17\t2\tlib/eddies.rb"
51
+ ]
52
+ end
53
+ end
54
+
55
+ describe "::counted_line_changes_by_file_by_commit" do
56
+ before do
57
+ calculator.stub(:git_log_file_lines) {
58
+ [
59
+ "10\t6\tlib/turbulence.rb",
60
+ "17\t2\tlib/eddies.rb"
61
+ ]
62
+ }
63
+ end
64
+
65
+ it "sums up the line changes" do
66
+ calculator.counted_line_changes_by_file_by_commit.should =~ [["lib/turbulence.rb", 16], ["lib/eddies.rb", 19]]
67
+ end
68
+ end
69
+
70
+ context "Full stack tests" do
71
+ context "when one ruby file is given" do
72
+ context "with two log entries for file" do
73
+ before do
74
+ calculator.stub(:git_log_command) do
75
+ "\n\n\n\n10\t6\tlib/turbulence.rb\n" +
76
+ "\n\n\n\n11\t7\tlib/turbulence.rb\n"
77
+ end
78
+ end
79
+ it "gives the line change count for the file" do
80
+ yielded_files = []
81
+ calculator.for_these_files(["lib/turbulence.rb"]) do |filename, score|
82
+ yielded_files << [filename, score]
83
+ end
84
+ yielded_files.should =~ [["lib/turbulence.rb", 16]]
85
+ end
86
+ context "with only one log entry for file" do
87
+ before do
88
+ calculator.stub(:git_log_command) do
89
+ "\n\n\n\n10\t6\tlib/turbulence.rb\n"
90
+ end
91
+ end
92
+ it "shows zero churn for the file" do
93
+ yielded_files = []
94
+ calculator.for_these_files(["lib/turbulence.rb"]) do |filename, score|
95
+ yielded_files << [filename, score]
96
+ end
97
+ yielded_files.should =~ [["lib/turbulence.rb", 0]]
98
+ end
99
+ end
100
+ end
101
+ end
102
+ end
103
+ end
@@ -0,0 +1,37 @@
1
+ require 'turbulence/calculators/complexity'
2
+
3
+ describe Turbulence::Calculators::Complexity do
4
+ let(:calculator) { Turbulence::Calculators::Complexity }
5
+ describe "::for_these_files" do
6
+ it "yields up the filename and score for each file" do
7
+ files = ["lib/corey.rb", "lib/chad.rb"]
8
+ calculator.stub(:score_for_file) { |filename|
9
+ filename.size
10
+ }
11
+ yielded_files = []
12
+ calculator.for_these_files(files) do |filename, score|
13
+ yielded_files << [filename, score]
14
+ end
15
+ yielded_files.should =~ [["lib/corey.rb", 12],
16
+ ["lib/chad.rb",11]]
17
+ end
18
+ end
19
+ end
20
+
21
+ describe Turbulence::Calculators::Complexity::Reporter do
22
+ subject { Turbulence::Calculators::Complexity::Reporter.new }
23
+ it "uses the total value from flog" do
24
+ flog_output = <<-FLOG_OUTPUT
25
+ 38.7: flog total
26
+ 5.5: flog/method average
27
+
28
+ 9.3: Turbulence#initialize lib/turbulence.rb:9
29
+ 8.7: Turbulence#churn lib/turbulence.rb:41
30
+ 6.1: Turbulence#complexity lib/turbulence.rb:26
31
+ FLOG_OUTPUT
32
+
33
+ subject.stub(:string) { flog_output }
34
+
35
+ subject.score.should == 38.7
36
+ end
37
+ end
@@ -22,18 +22,19 @@ $(document).ready(function() {
22
22
  enabled: true,
23
23
  text: 'Churn'
24
24
  },
25
- startOnTick: true,
26
- endOnTick: true,
27
- showLastLabel: true,
28
- min: 0
25
+ startOnTick: false,
26
+ endOnTick: false,
27
+ labels: {enabled:false},
28
+ lineWidth: 0,
29
+ tickWidth: 0
29
30
  },
30
31
  yAxis: {
31
32
  title: {
32
33
  text: 'Complexity'
33
34
  },
34
- min: 0,
35
- max: 50
36
-
35
+ startOnTick: false,
36
+ endOnTick: false,
37
+ labels: {enabled:false}
37
38
  },
38
39
  tooltip: {
39
40
  formatter: function() {
metadata CHANGED
@@ -5,8 +5,8 @@ version: !ruby/object:Gem::Version
5
5
  segments:
6
6
  - 0
7
7
  - 0
8
- - 3
9
- version: 0.0.3
8
+ - 4
9
+ version: 0.0.4
10
10
  platform: ruby
11
11
  authors:
12
12
  - Chad Fowler
@@ -16,7 +16,7 @@ autorequire:
16
16
  bindir: bin
17
17
  cert_chain: []
18
18
 
19
- date: 2011-02-27 00:00:00 -07:00
19
+ date: 2011-02-28 00:00:00 -07:00
20
20
  default_executable:
21
21
  dependencies:
22
22
  - !ruby/object:Gem::Dependency
@@ -84,8 +84,13 @@ files:
84
84
  - Rakefile
85
85
  - bin/bule
86
86
  - lib/turbulence.rb
87
+ - lib/turbulence/calculators/churn.rb
88
+ - lib/turbulence/calculators/complexity.rb
89
+ - lib/turbulence/command_line_interface.rb
87
90
  - lib/turbulence/scatter_plot_generator.rb
88
91
  - lib/turbulence/version.rb
92
+ - spec/turbulence/calculators/churn_spec.rb
93
+ - spec/turbulence/calculators/complexity_spec.rb
89
94
  - template/highchart_template.js.erb
90
95
  - template/highcharts.js
91
96
  - template/jquery.min.js
@@ -123,5 +128,6 @@ rubygems_version: 1.3.7
123
128
  signing_key:
124
129
  specification_version: 3
125
130
  summary: Automates churn + flog scoring on a git repo for a Ruby project
126
- test_files: []
127
-
131
+ test_files:
132
+ - spec/turbulence/calculators/churn_spec.rb
133
+ - spec/turbulence/calculators/complexity_spec.rb