turbulence 0.0.3 → 0.0.4

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/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