turbulence 1.1.0 → 1.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.
@@ -0,0 +1,15 @@
1
+ ---
2
+ !binary "U0hBMQ==":
3
+ metadata.gz: !binary |-
4
+ ZWM3NDQzMzE3NGIwODMyNGE0YmJmZjg1N2E1OThhNzY0M2M3NzNhNQ==
5
+ data.tar.gz: !binary |-
6
+ MTM3OTcwYmY3MjhmNTljZmI4M2RhMTA0MjVhOTliYjdiNTNmNDM5OA==
7
+ !binary "U0hBNTEy":
8
+ metadata.gz: !binary |-
9
+ MzdhYzRjNGNmMGQwODEwZTk2ZWNiMzRjMmZjMzUyNzgzY2IzNWY5YWZlODFm
10
+ MmJlYjNjODAwMDE4YmYyNzlkZDM2NTZkNzY2OWU5NjJmNDVmYzE3OWJlN2I4
11
+ YmIyOWZhMGMwOTFhOWJjZWIxY2MwYWU4ODlkODMxNjZiM2I4ZDk=
12
+ data.tar.gz: !binary |-
13
+ ODk3MmQ1MmUwZmIyOTRkMTRhMjkzYjNjM2M0ZmY0NmQ5ZTMxMDg0OTBiZTIw
14
+ NjQ1NDVjNjk5OTgxMWQyMTNlMzZhMmU3NzdlMmFjNTJjYTJlOGEyYjhhZjNl
15
+ MTQ0Mzc0ZDk1ZGFiZjBjNzZlNTAxZDk0YWU3MDAwODE1YTliMDA=
@@ -0,0 +1,6 @@
1
+ rvm:
2
+ - "1.9.3"
3
+ - "1.9.2"
4
+ - "1.8.7"
5
+ - jruby-18mode
6
+ - jruby-19mode
@@ -1,22 +1,23 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- turbulence (1.0.0)
5
- flog (= 3.2.1)
4
+ turbulence (1.2.0)
5
+ flog (~> 4.1)
6
6
  json (>= 1.4.6)
7
- launchy (~> 2.0.0)
7
+ launchy (>= 2.0.0)
8
8
 
9
9
  GEM
10
10
  remote: http://rubygems.org/
11
11
  specs:
12
- addressable (2.2.8)
12
+ addressable (2.3.5)
13
13
  diff-lcs (1.1.3)
14
- flog (3.2.1)
14
+ flog (4.1.2)
15
15
  ruby_parser (~> 3.1, > 3.1.0)
16
16
  sexp_processor (~> 4.0)
17
- json (1.7.6)
18
- launchy (2.0.5)
19
- addressable (~> 2.2.6)
17
+ json (1.8.0)
18
+ launchy (2.3.0)
19
+ addressable (~> 2.3)
20
+ rake (10.1.0)
20
21
  rspec (2.6.0)
21
22
  rspec-core (~> 2.6.0)
22
23
  rspec-expectations (~> 2.6.0)
@@ -25,14 +26,14 @@ GEM
25
26
  rspec-expectations (2.6.0)
26
27
  diff-lcs (~> 1.1.2)
27
28
  rspec-mocks (2.6.0)
28
- ruby_parser (3.1.1)
29
+ ruby_parser (3.2.2)
29
30
  sexp_processor (~> 4.1)
30
- sexp_processor (4.1.3)
31
+ sexp_processor (4.3.0)
31
32
 
32
33
  PLATFORMS
33
34
  ruby
34
- x86-mingw32
35
35
 
36
36
  DEPENDENCIES
37
+ rake
37
38
  rspec (~> 2.6.0)
38
39
  turbulence!
data/README.md CHANGED
@@ -3,6 +3,25 @@ Hopefully-meaningful Metrics
3
3
 
4
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.
5
5
 
6
+ Here is how to read the graph (extracted from the above article):
7
+
8
+ * The upper right quadrant is particularly important.
9
+ These files have a high degree of complexity, and they change quite frequently.
10
+ There are a number of reasons why this can happen.
11
+ The one to look out for, though, is something I call runaway conditionals.
12
+ Sometimes a class becomes so complex that refactoring seems too difficult.
13
+ Developers hack if-then-elses into if-then-elses, and the rat’s nest grows. These classes are particularly ripe for a refactoring investment.
14
+
15
+ * The lower left quadrant. is the healthy closure region.
16
+ Abstractions here have low complexity and don't change much.
17
+
18
+ * The upper left is what I call the cowboy region. This is complex code that sprang from someone's head and didn't seem to grow incrementally.
19
+
20
+ * The bottom right is very interesting. I call it the fertile ground.
21
+ It can consist of files that are somewhat configurational, but often there are also files that act as incubators for new abstractions.
22
+ People add code, it grows, and then they factor outward, extracting new classes. The files churn frequently, but their complexity remains low.
23
+
24
+
6
25
  Installation
7
26
  ------------
8
27
 
data/Rakefile CHANGED
@@ -1,8 +1,11 @@
1
1
  require 'bundler'
2
2
  require File.join(File.dirname(__FILE__), 'win_rakefile_location_fix')
3
+
3
4
  Bundler::GemHelper.install_tasks
5
+
4
6
  require 'rake'
5
7
  require 'rspec/core/rake_task'
8
+
6
9
  RSpec::Core::RakeTask.new(:spec) do |t|
7
10
  t.skip_bundler = true
8
11
  end
@@ -1,19 +1,30 @@
1
- require 'turbulence/scatter_plot_generator'
1
+ require 'turbulence/file_name_mangler'
2
2
  require 'turbulence/command_line_interface'
3
3
  require 'turbulence/checks_environment'
4
4
  require 'turbulence/calculators/churn'
5
5
  require 'turbulence/calculators/complexity'
6
+ require 'turbulence/generators/treemap'
7
+ require 'turbulence/generators/scatterplot'
6
8
 
7
9
  class Turbulence
8
- CODE_DIRECTORIES = ["app/models", "app/controllers", "app/helpers", "app/jobs", "app/mailers", "app/validators", "lib"]
9
- CALCULATORS = [Turbulence::Calculators::Complexity, Turbulence::Calculators::Churn]
10
+ CODE_DIRECTORIES = ["app/models",
11
+ "app/controllers",
12
+ "app/helpers",
13
+ "app/jobs",
14
+ "app/mailers",
15
+ "app/validators",
16
+ "lib"]
17
+ CALCULATORS = [Turbulence::Calculators::Complexity,
18
+ Turbulence::Calculators::Churn]
10
19
 
11
20
  attr_reader :exclusion_pattern
12
21
  attr_reader :metrics
22
+
13
23
  def initialize(directory, output = nil, exclusion_pattern = nil)
14
- @output = output
15
- @metrics = {}
24
+ @output = output
25
+ @metrics = {}
16
26
  @exclusion_pattern = exclusion_pattern
27
+
17
28
  Dir.chdir(directory) do
18
29
  CALCULATORS.each(&method(:calculate_metrics_with))
19
30
  end
@@ -26,10 +37,12 @@ class Turbulence
26
37
 
27
38
  def calculate_metrics_with(calculator)
28
39
  report "calculating metric: #{calculator}\n"
40
+
29
41
  calculator.for_these_files(files_of_interest) do |filename, score|
30
42
  report "."
31
43
  set_file_metric(filename, calculator, score)
32
44
  end
45
+
33
46
  report "\n"
34
47
  end
35
48
 
@@ -29,17 +29,9 @@ class Turbulence
29
29
  end
30
30
 
31
31
  def score_for_file(filename)
32
+ flogger.reset
32
33
  flogger.flog filename
33
- reporter = Reporter.new
34
- flogger.report(reporter)
35
- reporter.score
36
- end
37
- end
38
-
39
- class Reporter < ::StringIO
40
- SCORE_LINE_DETECTOR = /^\s+([^:]+).*flog total$/
41
- def score
42
- Float(string.scan(SCORE_LINE_DETECTOR).flatten.first)
34
+ flogger.total_score
43
35
  end
44
36
  end
45
37
  end
@@ -7,10 +7,13 @@ require 'turbulence/scm/perforce'
7
7
  class Turbulence
8
8
  class CommandLineInterface
9
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|
10
+ TEMPLATE_FILES = ['turbulence.html',
11
+ 'highcharts.js',
12
+ 'jquery.min.js',
13
+ 'treemap.html'].map do |filename|
11
14
  File.join(TURBULENCE_TEMPLATE_PATH, filename)
12
- }
13
-
15
+ end
16
+
14
17
  attr_reader :exclusion_pattern
15
18
  attr_reader :directory
16
19
  def initialize(argv)
@@ -25,16 +28,24 @@ class Turbulence
25
28
  Turbulence::Calculators::Churn.scm = Scm::Perforce
26
29
  end
27
30
  end
31
+
28
32
  opts.on('--churn-range since..until', String, 'commit range to compute file churn') do |s|
29
33
  Turbulence::Calculators::Churn.commit_range = s
30
34
  end
35
+
31
36
  opts.on('--churn-mean', 'calculate mean churn instead of cummulative') do
32
37
  Turbulence::Calculators::Churn.compute_mean = true
33
38
  end
39
+
34
40
  opts.on('--exclude pattern', String, 'exclude files matching pattern') do |pattern|
35
41
  @exclusion_pattern = pattern
36
42
  end
37
43
 
44
+ opts.on('--treemap', String, 'output treemap graph instead of scatterplot') do |s|
45
+ @graph_type = "treemap"
46
+ end
47
+
48
+
38
49
  opts.on_tail("-h", "--help", "Show this message") do
39
50
  puts opts
40
51
  exit
@@ -47,21 +58,31 @@ class Turbulence
47
58
  def copy_templates_into(directory)
48
59
  FileUtils.cp TEMPLATE_FILES, directory
49
60
  end
50
- private :copy_templates_into
51
61
 
52
62
  def generate_bundle
53
63
  FileUtils.mkdir_p("turbulence")
64
+
54
65
  Dir.chdir("turbulence") do
55
- copy_templates_into(Dir.pwd)
56
- File.open("cc.js", "w") do |f|
57
- turb = Turbulence.new(directory,STDOUT, @exclusion_pattern)
58
- f.write Turbulence::ScatterPlotGenerator.from(turb.metrics).to_js
66
+ turb = Turbulence.new(directory,STDOUT, @exclusion_pattern)
67
+
68
+ generator = case @graph_type
69
+ when "treemap"
70
+ Turbulence::Generators::TreeMap.new({})
71
+ else
72
+ Turbulence::Generators::ScatterPlot.new({})
59
73
  end
74
+
75
+ generator.generate_results(turb.metrics, self)
60
76
  end
61
77
  end
62
78
 
63
79
  def open_bundle
64
- Launchy.open("file://#{directory}/turbulence/turbulence.html")
80
+ case @graph_type
81
+ when "treemap"
82
+ Launchy.open("file://#{directory}/turbulence/treemap.html")
83
+ else
84
+ Launchy.open("file://#{directory}/turbulence/turbulence.html")
85
+ end
65
86
  end
66
87
  end
67
88
  end
@@ -0,0 +1,18 @@
1
+ require 'json'
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
+ end
@@ -0,0 +1,65 @@
1
+ class Turbulence
2
+ module Generators
3
+ class ScatterPlot
4
+ attr_reader :metrics_hash, :x_metric, :y_metric
5
+
6
+ def initialize(metrics_hash,
7
+ x_metric = Turbulence::Calculators::Churn,
8
+ y_metric = Turbulence::Calculators::Complexity)
9
+ @x_metric = x_metric
10
+ @y_metric = y_metric
11
+ @metrics_hash = metrics_hash
12
+ end
13
+
14
+ def self.from(metrics_hash)
15
+ new(metrics_hash)
16
+ end
17
+
18
+ def mangle
19
+ mangler = FileNameMangler.new
20
+ mangled = {}
21
+ metrics_hash.each_pair { |filename, metrics| mangled[mangler.mangle_name(filename)] = metrics}
22
+ @metrics_hash = mangled
23
+ end
24
+
25
+ def to_js
26
+ clean_metrics_from_missing_data
27
+ directory_series = {}
28
+
29
+ grouped_by_directory.each_pair do |directory, metrics_hash|
30
+ directory_series[directory] = file_metrics_for_directory(metrics_hash)
31
+ end
32
+
33
+ "var directorySeries = #{directory_series.to_json};"
34
+ end
35
+
36
+ def clean_metrics_from_missing_data
37
+ metrics_hash.reject! do |filename, metrics|
38
+ metrics[x_metric].nil? || metrics[y_metric].nil?
39
+ end
40
+ end
41
+
42
+ def grouped_by_directory
43
+ metrics_hash.group_by do |filename, _|
44
+ directories = File.dirname(filename).split("/")
45
+ directories[0..1].join("/")
46
+ end
47
+ end
48
+
49
+ def file_metrics_for_directory(metrics_hash)
50
+ metrics_hash.map do |filename, metrics|
51
+ { :filename => filename,
52
+ :x => metrics[x_metric],
53
+ :y => metrics[y_metric]}
54
+ end
55
+ end
56
+
57
+ def generate_results(metrics, ci)
58
+ File.open("cc.js", "w") do |f|
59
+ ci.copy_templates_into(Dir.pwd)
60
+ f.write Turbulence::Generators::ScatterPlot.from(metrics).to_js
61
+ end
62
+ end
63
+ end
64
+ end
65
+ end
@@ -0,0 +1,48 @@
1
+ class Turbulence
2
+ module Generators
3
+ class TreeMap
4
+ attr_reader :metrics_hash, :x_metric, :y_metric
5
+
6
+ def initialize(metrics_hash,
7
+ x_metric = Turbulence::Calculators::Churn,
8
+ y_metric = Turbulence::Calculators::Complexity)
9
+ @x_metric = x_metric
10
+ @y_metric = y_metric
11
+ @metrics_hash = metrics_hash
12
+ end
13
+
14
+ def generate_results(metrics, cli)
15
+ File.open("treemap_data.js", "w") do |f|
16
+ cli.copy_templates_into(Dir.pwd)
17
+ f.write Turbulence::Generators::TreeMap.from(metrics).build_js
18
+ end
19
+ end
20
+
21
+ def build_js
22
+ clean_metrics_from_missing_data
23
+
24
+ output = "var treemap_data = [['File', 'Parent', 'Churn (size)', 'Complexity (color)'],\n"
25
+ output << "['Root', null, 0, 0],\n"
26
+
27
+ @metrics_hash.each do |file|
28
+ output << "['#{file[0]}', 'Root', #{file[1][@x_metric]}, #{file[1][@y_metric]}],\n"
29
+ end
30
+
31
+ output << "];"
32
+
33
+ output
34
+ end
35
+
36
+ private
37
+ def clean_metrics_from_missing_data
38
+ @metrics_hash.reject! do |filename, metrics|
39
+ metrics[@x_metric].nil? || metrics[@y_metric].nil?
40
+ end
41
+ end
42
+
43
+ def self.from(metrics_hash)
44
+ new(metrics_hash)
45
+ end
46
+ end
47
+ end
48
+ end
@@ -1,3 +1,3 @@
1
1
  class Turbulence
2
- VERSION = "1.1.0"
2
+ VERSION = "1.2.0"
3
3
  end
@@ -18,20 +18,3 @@ describe Turbulence::Calculators::Complexity do
18
18
  end
19
19
  end
20
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
@@ -14,7 +14,11 @@ describe Turbulence::CommandLineInterface do
14
14
  end
15
15
  it "bundles the files" do
16
16
  cli.generate_bundle
17
- Dir.glob('turbulence/*').sort.should eq(["turbulence/cc.js", "turbulence/highcharts.js", "turbulence/jquery.min.js", "turbulence/turbulence.html"])
17
+ Dir.glob('turbulence/*').sort.should eq(["turbulence/cc.js",
18
+ "turbulence/highcharts.js",
19
+ "turbulence/jquery.min.js",
20
+ "turbulence/treemap.html",
21
+ "turbulence/turbulence.html"])
18
22
  end
19
23
 
20
24
  it "passes along exclusion pattern" do
@@ -33,7 +37,7 @@ describe Turbulence::CommandLineInterface do
33
37
  cli_churn_range.directory.should == 'path/to/compute'
34
38
  Turbulence::Calculators::Churn.commit_range.should == 'f3e1d7a6..830b9d3d9f'
35
39
  end
36
-
40
+
37
41
  it "sets churn mean" do
38
42
  cli_churn_mean.directory.should == '.'
39
43
  Turbulence::Calculators::Churn.compute_mean.should be_true
@@ -1,49 +1,47 @@
1
1
  require 'turbulence'
2
2
 
3
- describe Turbulence::ScatterPlotGenerator do
3
+ describe Turbulence::Generators::ScatterPlot do
4
4
  context "with both Metrics" do
5
5
  it "generates JavaScript" do
6
- generator = Turbulence::ScatterPlotGenerator.new(
7
- "foo.rb" => {
8
- Turbulence::Calculators::Churn => 1,
9
- Turbulence::Calculators::Complexity => 2
10
- }
6
+ generator = Turbulence::Generators::ScatterPlot.new(
7
+ "foo.rb" => { Turbulence::Calculators::Churn => 1,
8
+ Turbulence::Calculators::Complexity => 2 }
11
9
  )
12
- generator.to_js.should =~ /var directorySeries/
13
- generator.to_js.should =~ /\"filename\"\:\"foo.rb\"/
14
- generator.to_js.should =~ /\"x\":1/
15
- generator.to_js.should =~ /\"y\":2/
16
- end
10
+
11
+ generator.to_js.should =~ /var directorySeries/
12
+ generator.to_js.should =~ /\"filename\"\:\"foo.rb\"/
13
+ generator.to_js.should =~ /\"x\":1/
14
+ generator.to_js.should =~ /\"y\":2/
15
+ end
17
16
  end
18
17
 
19
18
  context "with a missing Metric" do
20
19
  it "generates JavaScript" do
21
- generator = Turbulence::ScatterPlotGenerator.new(
22
- "foo.rb" => {
23
- Turbulence::Calculators::Churn => 1
24
- }
20
+ generator = Turbulence::Generators::ScatterPlot.new(
21
+ "foo.rb" => { Turbulence::Calculators::Churn => 1 }
25
22
  )
23
+
26
24
  generator.to_js.should == 'var directorySeries = {};'
27
- end
25
+ end
28
26
  end
29
27
 
30
- describe "#clean_metrics_from_missing_data" do
31
- let(:spg) {Turbulence::ScatterPlotGenerator.new({})}
28
+ describe "#clean_metrics_from_missing_data" do
29
+ let(:spg) {Turbulence::Generators::ScatterPlot.new({})}
32
30
 
33
- it "removes entries with missing churn" do
34
- spg.stub(:metrics_hash).and_return("foo.rb" => {
31
+ it "removes entries with missing churn" do
32
+ spg.stub(:metrics_hash).and_return("foo.rb" => {
35
33
  Turbulence::Calculators::Complexity => 88.3})
36
34
  spg.clean_metrics_from_missing_data.should == {}
37
35
  end
38
36
 
39
37
  it "removes entries with missing complexity" do
40
- spg.stub(:metrics_hash).and_return("foo.rb" => {
38
+ spg.stub(:metrics_hash).and_return("foo.rb" => {
41
39
  Turbulence::Calculators::Churn => 1})
42
40
  spg.clean_metrics_from_missing_data.should == {}
43
41
  end
44
42
 
45
43
  it "keeps entries with churn and complexity present" do
46
- spg.stub(:metrics_hash).and_return("foo.rb" => {
44
+ spg.stub(:metrics_hash).and_return("foo.rb" => {
47
45
  Turbulence::Calculators::Churn => 1,
48
46
  Turbulence::Calculators::Complexity => 88.3})
49
47
  spg.clean_metrics_from_missing_data.should_not == {}
@@ -51,7 +49,7 @@ describe Turbulence::ScatterPlotGenerator do
51
49
  end
52
50
 
53
51
  describe "#grouped_by_directory" do
54
- let(:spg) {Turbulence::ScatterPlotGenerator.new("lib/foo/foo.rb" => {
52
+ let(:spg) {Turbulence::Generators::ScatterPlot.new("lib/foo/foo.rb" => {
55
53
  Turbulence::Calculators::Churn => 1},
56
54
  "lib/bar.rb" => {
57
55
  Turbulence::Calculators::Churn => 2} )}
@@ -60,17 +58,17 @@ describe Turbulence::ScatterPlotGenerator do
60
58
  spg.stub(:metrics_hash).and_return("foo.rb" => {
61
59
  Turbulence::Calculators::Churn => 1
62
60
  })
63
- spg.grouped_by_directory.should == {"." => [["foo.rb", Turbulence::Calculators::Churn => 1]]}
61
+ spg.grouped_by_directory.should == {"." => [["foo.rb", {Turbulence::Calculators::Churn => 1}]]}
64
62
  end
65
63
 
66
64
  it "takes full path into account" do
67
- spg.grouped_by_directory.should == {"lib/foo" => [["lib/foo/foo.rb", Turbulence::Calculators::Churn => 1]],
68
- "lib" => [["lib/bar.rb", Turbulence::Calculators::Churn => 2]]}
65
+ spg.grouped_by_directory.should == {"lib/foo" => [["lib/foo/foo.rb", {Turbulence::Calculators::Churn => 1}]],
66
+ "lib" => [["lib/bar.rb", {Turbulence::Calculators::Churn => 2}]]}
69
67
  end
70
68
  end
71
69
 
72
70
  describe "#file_metrics_for_directory" do
73
- let(:spg) {Turbulence::ScatterPlotGenerator.new({})}
71
+ let(:spg) {Turbulence::Generators::ScatterPlot.new({})}
74
72
  it "assigns :filename, :x, :y" do
75
73
  spg.file_metrics_for_directory("lib/foo/foo.rb" => {
76
74
  Turbulence::Calculators::Churn => 1,
@@ -0,0 +1,26 @@
1
+ require 'turbulence'
2
+
3
+ describe Turbulence::Generators::TreeMap do
4
+ context "with both Metrics" do
5
+ it "generates JavaScript" do
6
+ generator = Turbulence::Generators::TreeMap.new(
7
+ "foo.rb" => { Turbulence::Calculators::Churn => 1,
8
+ Turbulence::Calculators::Complexity => 2 }
9
+ )
10
+
11
+ generator.build_js.should =~ /var treemap_data/
12
+ generator.build_js.should =~ /\'foo.rb\'/
13
+ end
14
+ end
15
+
16
+ context "with a missing Metric" do
17
+ it "generates JavaScript" do
18
+ generator = Turbulence::Generators::TreeMap.new(
19
+ "foo.rb" => { Turbulence::Calculators::Churn => 1 }
20
+ )
21
+
22
+ generator.build_js.should == "var treemap_data = [['File', 'Parent', 'Churn (size)', 'Complexity (color)'],\n['Root', null, 0, 0],\n];"
23
+ end
24
+ end
25
+ end
26
+
@@ -0,0 +1,29 @@
1
+ <html>
2
+ <head>
3
+ <script src="treemap_data.js"></script>
4
+ <script type="text/javascript" src="https://www.google.com/jsapi"></script>
5
+ <script type="text/javascript">
6
+ google.load("visualization", "1", {packages:["treemap"]});
7
+ google.setOnLoadCallback(drawChart);
8
+ function drawChart() {
9
+ // Create and populate the data table.
10
+ var data = google.visualization.arrayToDataTable(treemap_data);
11
+
12
+ // Create and draw the visualization.
13
+ var tree = new google.visualization.TreeMap(document.getElementById('chart_div'));
14
+ tree.draw(data, {
15
+ minColor: '#0d0',
16
+ midColor: '#ddd',
17
+ maxColor: '#f00',
18
+ headerHeight: 15,
19
+ fontColor: 'black',
20
+ showScale: true,
21
+ showTooltips: true});
22
+ }
23
+ </script>
24
+ </head>
25
+
26
+ <body>
27
+ <div id="chart_div" style="width: 900px; height: 500px;"></div>
28
+ </body>
29
+ </html>
@@ -9,10 +9,11 @@ Gem::Specification.new do |s|
9
9
  s.authors = ["Chad Fowler", "Michael Feathers", "Corey Haines"]
10
10
  s.email = ["chad@chadfowler.com", "mfeathers@obtiva.com", "coreyhaines@gmail.com"]
11
11
  s.homepage = "http://chadfowler.com"
12
- s.add_dependency "flog", "= 3.2.1"
12
+ s.add_dependency "flog", "~>4.1"
13
13
  s.add_dependency "json", ">= 1.4.6"
14
- s.add_dependency "launchy", "~> 2.0.0"
14
+ s.add_dependency "launchy", ">= 2.0.0"
15
15
  s.add_development_dependency 'rspec', '~> 2.6.0'
16
+ s.add_development_dependency 'rake'
16
17
 
17
18
  s.summary = %q{Automates churn + flog scoring on a git repo for a Ruby project}
18
19
  s.description = %q{Based on this http://www.stickyminds.com/sitewide.asp?Function=edetail&ObjectType=COL&ObjectId=16679&tth=DYN&tt=siteemail&iDyn=2}
metadata CHANGED
@@ -1,8 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: turbulence
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.1.0
5
- prerelease:
4
+ version: 1.2.0
6
5
  platform: ruby
7
6
  authors:
8
7
  - Chad Fowler
@@ -11,28 +10,25 @@ authors:
11
10
  autorequire:
12
11
  bindir: bin
13
12
  cert_chain: []
14
- date: 2013-01-10 00:00:00.000000000 Z
13
+ date: 2013-09-07 00:00:00.000000000 Z
15
14
  dependencies:
16
15
  - !ruby/object:Gem::Dependency
17
16
  name: flog
18
17
  requirement: !ruby/object:Gem::Requirement
19
- none: false
20
18
  requirements:
21
- - - '='
19
+ - - ~>
22
20
  - !ruby/object:Gem::Version
23
- version: 3.2.1
21
+ version: '4.1'
24
22
  type: :runtime
25
23
  prerelease: false
26
24
  version_requirements: !ruby/object:Gem::Requirement
27
- none: false
28
25
  requirements:
29
- - - '='
26
+ - - ~>
30
27
  - !ruby/object:Gem::Version
31
- version: 3.2.1
28
+ version: '4.1'
32
29
  - !ruby/object:Gem::Dependency
33
30
  name: json
34
31
  requirement: !ruby/object:Gem::Requirement
35
- none: false
36
32
  requirements:
37
33
  - - ! '>='
38
34
  - !ruby/object:Gem::Version
@@ -40,7 +36,6 @@ dependencies:
40
36
  type: :runtime
41
37
  prerelease: false
42
38
  version_requirements: !ruby/object:Gem::Requirement
43
- none: false
44
39
  requirements:
45
40
  - - ! '>='
46
41
  - !ruby/object:Gem::Version
@@ -48,23 +43,20 @@ dependencies:
48
43
  - !ruby/object:Gem::Dependency
49
44
  name: launchy
50
45
  requirement: !ruby/object:Gem::Requirement
51
- none: false
52
46
  requirements:
53
- - - ~>
47
+ - - ! '>='
54
48
  - !ruby/object:Gem::Version
55
49
  version: 2.0.0
56
50
  type: :runtime
57
51
  prerelease: false
58
52
  version_requirements: !ruby/object:Gem::Requirement
59
- none: false
60
53
  requirements:
61
- - - ~>
54
+ - - ! '>='
62
55
  - !ruby/object:Gem::Version
63
56
  version: 2.0.0
64
57
  - !ruby/object:Gem::Dependency
65
58
  name: rspec
66
59
  requirement: !ruby/object:Gem::Requirement
67
- none: false
68
60
  requirements:
69
61
  - - ~>
70
62
  - !ruby/object:Gem::Version
@@ -72,80 +64,148 @@ dependencies:
72
64
  type: :development
73
65
  prerelease: false
74
66
  version_requirements: !ruby/object:Gem::Requirement
75
- none: false
76
67
  requirements:
77
68
  - - ~>
78
69
  - !ruby/object:Gem::Version
79
70
  version: 2.6.0
71
+ - !ruby/object:Gem::Dependency
72
+ name: rake
73
+ requirement: !ruby/object:Gem::Requirement
74
+ requirements:
75
+ - - ! '>='
76
+ - !ruby/object:Gem::Version
77
+ version: '0'
78
+ type: :development
79
+ prerelease: false
80
+ version_requirements: !ruby/object:Gem::Requirement
81
+ requirements:
82
+ - - ! '>='
83
+ - !ruby/object:Gem::Version
84
+ version: '0'
80
85
  description: Based on this http://www.stickyminds.com/sitewide.asp?Function=edetail&ObjectType=COL&ObjectId=16679&tth=DYN&tt=siteemail&iDyn=2
81
86
  email:
82
87
  - chad@chadfowler.com
83
88
  - mfeathers@obtiva.com
84
89
  - coreyhaines@gmail.com
85
90
  executables:
86
- - bule
91
+ - !binary |-
92
+ YnVsZQ==
87
93
  extensions: []
88
94
  extra_rdoc_files: []
89
95
  files:
90
- - .gitignore
91
- - Gemfile
92
- - Gemfile.lock
93
- - README.md
94
- - Rakefile
95
- - bin/bule
96
- - lib/turbulence.rb
97
- - lib/turbulence/calculators/churn.rb
98
- - lib/turbulence/calculators/complexity.rb
99
- - lib/turbulence/checks_environment.rb
100
- - lib/turbulence/command_line_interface.rb
101
- - lib/turbulence/scatter_plot_generator.rb
102
- - lib/turbulence/scm/git.rb
103
- - lib/turbulence/scm/perforce.rb
104
- - lib/turbulence/version.rb
105
- - spec/turbulence/calculators/churn_spec.rb
106
- - spec/turbulence/calculators/complexity_spec.rb
107
- - spec/turbulence/checks_environoment_spec.rb
108
- - spec/turbulence/command_line_interface_spec.rb
109
- - spec/turbulence/scatter_splot_generator_spec.rb
110
- - spec/turbulence/scm/git_spec.rb
111
- - spec/turbulence/scm/perforce_spec.rb
112
- - spec/turbulence/turbulence_spec.rb
113
- - template/highchart_template.js.erb
114
- - template/highcharts.js
115
- - template/jquery.min.js
116
- - template/turbulence.html
117
- - turbulence.gemspec
118
- - win_rakefile_location_fix.rb
96
+ - !binary |-
97
+ LmdpdGlnbm9yZQ==
98
+ - !binary |-
99
+ LnRyYXZpcy55bWw=
100
+ - !binary |-
101
+ R2VtZmlsZQ==
102
+ - !binary |-
103
+ R2VtZmlsZS5sb2Nr
104
+ - !binary |-
105
+ UkVBRE1FLm1k
106
+ - !binary |-
107
+ UmFrZWZpbGU=
108
+ - !binary |-
109
+ YmluL2J1bGU=
110
+ - !binary |-
111
+ bGliL3R1cmJ1bGVuY2UucmI=
112
+ - !binary |-
113
+ bGliL3R1cmJ1bGVuY2UvY2FsY3VsYXRvcnMvY2h1cm4ucmI=
114
+ - !binary |-
115
+ bGliL3R1cmJ1bGVuY2UvY2FsY3VsYXRvcnMvY29tcGxleGl0eS5yYg==
116
+ - !binary |-
117
+ bGliL3R1cmJ1bGVuY2UvY2hlY2tzX2Vudmlyb25tZW50LnJi
118
+ - !binary |-
119
+ bGliL3R1cmJ1bGVuY2UvY29tbWFuZF9saW5lX2ludGVyZmFjZS5yYg==
120
+ - !binary |-
121
+ bGliL3R1cmJ1bGVuY2UvZmlsZV9uYW1lX21hbmdsZXIucmI=
122
+ - !binary |-
123
+ bGliL3R1cmJ1bGVuY2UvZ2VuZXJhdG9ycy9zY2F0dGVycGxvdC5yYg==
124
+ - !binary |-
125
+ bGliL3R1cmJ1bGVuY2UvZ2VuZXJhdG9ycy90cmVlbWFwLnJi
126
+ - !binary |-
127
+ bGliL3R1cmJ1bGVuY2Uvc2NtL2dpdC5yYg==
128
+ - !binary |-
129
+ bGliL3R1cmJ1bGVuY2Uvc2NtL3BlcmZvcmNlLnJi
130
+ - !binary |-
131
+ bGliL3R1cmJ1bGVuY2UvdmVyc2lvbi5yYg==
132
+ - !binary |-
133
+ c3BlYy90dXJidWxlbmNlL2NhbGN1bGF0b3JzL2NodXJuX3NwZWMucmI=
134
+ - !binary |-
135
+ c3BlYy90dXJidWxlbmNlL2NhbGN1bGF0b3JzL2NvbXBsZXhpdHlfc3BlYy5y
136
+ Yg==
137
+ - !binary |-
138
+ c3BlYy90dXJidWxlbmNlL2NoZWNrc19lbnZpcm9ub21lbnRfc3BlYy5yYg==
139
+ - !binary |-
140
+ c3BlYy90dXJidWxlbmNlL2NvbW1hbmRfbGluZV9pbnRlcmZhY2Vfc3BlYy5y
141
+ Yg==
142
+ - !binary |-
143
+ c3BlYy90dXJidWxlbmNlL2dlbmVyYXRvcnMvc2NhdHRlcl9wbG90X3NwZWMu
144
+ cmI=
145
+ - !binary |-
146
+ c3BlYy90dXJidWxlbmNlL2dlbmVyYXRvcnMvdHJlZW1hcF9zcGVjLnJi
147
+ - !binary |-
148
+ c3BlYy90dXJidWxlbmNlL3NjbS9naXRfc3BlYy5yYg==
149
+ - !binary |-
150
+ c3BlYy90dXJidWxlbmNlL3NjbS9wZXJmb3JjZV9zcGVjLnJi
151
+ - !binary |-
152
+ c3BlYy90dXJidWxlbmNlL3R1cmJ1bGVuY2Vfc3BlYy5yYg==
153
+ - !binary |-
154
+ dGVtcGxhdGUvaGlnaGNoYXJ0X3RlbXBsYXRlLmpzLmVyYg==
155
+ - !binary |-
156
+ dGVtcGxhdGUvaGlnaGNoYXJ0cy5qcw==
157
+ - !binary |-
158
+ dGVtcGxhdGUvanF1ZXJ5Lm1pbi5qcw==
159
+ - !binary |-
160
+ dGVtcGxhdGUvdHJlZW1hcC5odG1s
161
+ - !binary |-
162
+ dGVtcGxhdGUvdHVyYnVsZW5jZS5odG1s
163
+ - !binary |-
164
+ dHVyYnVsZW5jZS5nZW1zcGVj
165
+ - !binary |-
166
+ d2luX3Jha2VmaWxlX2xvY2F0aW9uX2ZpeC5yYg==
119
167
  homepage: http://chadfowler.com
120
168
  licenses: []
169
+ metadata: {}
121
170
  post_install_message:
122
171
  rdoc_options: []
123
172
  require_paths:
124
173
  - lib
125
174
  required_ruby_version: !ruby/object:Gem::Requirement
126
- none: false
127
175
  requirements:
128
176
  - - ! '>='
129
177
  - !ruby/object:Gem::Version
130
178
  version: 1.8.7
131
179
  required_rubygems_version: !ruby/object:Gem::Requirement
132
- none: false
133
180
  requirements:
134
181
  - - ! '>='
135
182
  - !ruby/object:Gem::Version
136
183
  version: '0'
137
184
  requirements: []
138
185
  rubyforge_project: turbulence
139
- rubygems_version: 1.8.24
186
+ rubygems_version: 2.0.7
140
187
  signing_key:
141
- specification_version: 3
188
+ specification_version: 4
142
189
  summary: Automates churn + flog scoring on a git repo for a Ruby project
143
190
  test_files:
144
- - spec/turbulence/calculators/churn_spec.rb
145
- - spec/turbulence/calculators/complexity_spec.rb
146
- - spec/turbulence/checks_environoment_spec.rb
147
- - spec/turbulence/command_line_interface_spec.rb
148
- - spec/turbulence/scatter_splot_generator_spec.rb
149
- - spec/turbulence/scm/git_spec.rb
150
- - spec/turbulence/scm/perforce_spec.rb
151
- - spec/turbulence/turbulence_spec.rb
191
+ - !binary |-
192
+ c3BlYy90dXJidWxlbmNlL2NhbGN1bGF0b3JzL2NodXJuX3NwZWMucmI=
193
+ - !binary |-
194
+ c3BlYy90dXJidWxlbmNlL2NhbGN1bGF0b3JzL2NvbXBsZXhpdHlfc3BlYy5y
195
+ Yg==
196
+ - !binary |-
197
+ c3BlYy90dXJidWxlbmNlL2NoZWNrc19lbnZpcm9ub21lbnRfc3BlYy5yYg==
198
+ - !binary |-
199
+ c3BlYy90dXJidWxlbmNlL2NvbW1hbmRfbGluZV9pbnRlcmZhY2Vfc3BlYy5y
200
+ Yg==
201
+ - !binary |-
202
+ c3BlYy90dXJidWxlbmNlL2dlbmVyYXRvcnMvc2NhdHRlcl9wbG90X3NwZWMu
203
+ cmI=
204
+ - !binary |-
205
+ c3BlYy90dXJidWxlbmNlL2dlbmVyYXRvcnMvdHJlZW1hcF9zcGVjLnJi
206
+ - !binary |-
207
+ c3BlYy90dXJidWxlbmNlL3NjbS9naXRfc3BlYy5yYg==
208
+ - !binary |-
209
+ c3BlYy90dXJidWxlbmNlL3NjbS9wZXJmb3JjZV9zcGVjLnJi
210
+ - !binary |-
211
+ c3BlYy90dXJidWxlbmNlL3R1cmJ1bGVuY2Vfc3BlYy5yYg==
@@ -1,65 +0,0 @@
1
- require 'json'
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
-
19
- class ScatterPlotGenerator
20
- def self.from(metrics_hash)
21
- new(metrics_hash)
22
- end
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
27
- @metrics_hash = metrics_hash
28
- end
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
-
37
- def to_js
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, _|
54
- directories = File.dirname(filename).split("/")
55
- directories[0..1].join("/")
56
- end
57
- end
58
-
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]}
62
- end
63
- end
64
- end
65
- end