saikuro_treemap 0.1

Sign up to get free protection for your applications and to get access to all the features.
data/.gitignore ADDED
@@ -0,0 +1,3 @@
1
+ tmp
2
+ reports
3
+ *.gem
data/MIT-LICENSE.txt ADDED
@@ -0,0 +1,21 @@
1
+ The MIT License
2
+
3
+ Copyright (c) 2010 ThoughtWorks, Inc. (http://thoughtworks.com)
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in
13
+ all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ THE SOFTWARE.
data/README.textile ADDED
@@ -0,0 +1,65 @@
1
+ h2. Saikuro Treemap Generator
2
+
3
+ This gem generates a "treemap":http://en.wikipedia.org/wiki/Treemapping
4
+ showing the complexity of ruby code on which it is run. It uses
5
+ "Saikuro":http://saikuro.rubyforge.org/ under the covers to analyze ruby
6
+ code complexity.
7
+
8
+ The size of blocks in the treemap is representative of the size of the
9
+ code element represented by that block. The color of the block indicates
10
+ complexity of that particular code element. Green is good, red is bad,
11
+ blue is somewhere in between.
12
+
13
+ Small evenly distributed blocks which are green in color is indicative of
14
+ good code.
15
+
16
+ h2. Usage
17
+
18
+ We clearly need to make the gem install a bit easier, but for now:
19
+
20
+ * checkout this repos from github
21
+ * build the saikuro_treemap gem...
22
+ * >rake gem
23
+ * install saikuro_treemap gem...
24
+ * >sudo gem install saikuro_treemap
25
+ * add a rake task like below to your project, adjusting :code_dirs for your project:
26
+
27
+ <pre>
28
+ require 'saikuro_treemap'
29
+
30
+ namespace :metrics do
31
+ desc 'generate ccn treemap'
32
+ task :ccn_treemap do
33
+ SaikuroTreemap.generate_treemap :code_dirs => ['lib']
34
+ `open reports/saikuro_treemap.html`
35
+ end
36
+ end
37
+ </pre>
38
+
39
+ * run the task to see the treemap in your browser
40
+
41
+ h2. License
42
+
43
+ Saikuro Treemap Generator is MIT Licensed
44
+
45
+ The MIT License
46
+
47
+ Copyright (c) 2010 ThoughtWorks, Inc. (http://thoughtworks.com)
48
+
49
+ Permission is hereby granted, free of charge, to any person obtaining a copy
50
+ of this software and associated documentation files (the "Software"), to deal
51
+ in the Software without restriction, including without limitation the rights
52
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
53
+ copies of the Software, and to permit persons to whom the Software is
54
+ furnished to do so, subject to the following conditions:
55
+
56
+ The above copyright notice and this permission notice shall be included in
57
+ all copies or substantial portions of the Software.
58
+
59
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
60
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
61
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
62
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
63
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
64
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
65
+ THE SOFTWARE.
data/Rakefile ADDED
@@ -0,0 +1,62 @@
1
+ require 'rake'
2
+ require 'rake/testtask'
3
+
4
+ $: << 'lib'
5
+ require 'saikuro_treemap'
6
+
7
+ namespace :metrics do
8
+ desc 'generate ccn treemap'
9
+ task :ccn_treemap do
10
+ SaikuroTreemap.generate_treemap :code_dirs => ['lib']
11
+ `open reports/saikuro_treemap.html`
12
+ end
13
+ end
14
+
15
+ begin
16
+ require 'rubygems'
17
+ rescue LoadError
18
+ # Too bad.
19
+ else
20
+ task "saikuro_treemap.gemspec" do
21
+ spec = Gem::Specification.new do |s|
22
+ s.name = "saikuro_treemap"
23
+ s.version = 0.1
24
+ s.platform = Gem::Platform::RUBY
25
+ s.summary = "Generate CCN Treemap based on saikuro analysis"
26
+
27
+ s.description = <<-EOF
28
+ Generate CCN Treemap based on saikuro analysis
29
+ EOF
30
+ s.add_dependency 'json'
31
+ s.add_dependency 'Saikuro'
32
+
33
+ s.files = `git ls-files`.split("\n") + %w(saikuro_treemap.gemspec)
34
+ s.bindir = 'bin'
35
+ s.require_path = 'lib'
36
+ s.has_rdoc = false
37
+ s.extra_rdoc_files = ['README.textile']
38
+ s.test_files = Dir['test/{test,spec}_*.rb']
39
+
40
+ s.author = 'ThoughtWorks Studios'
41
+ s.email = 'studios@thoughtworks.com'
42
+ s.homepage = 'http://github.com/ThoughtWorksStudios/saikuro_treemap'
43
+ s.rubyforge_project = 'saikuro_treemap'
44
+ end
45
+
46
+ File.open("saikuro_treemap.gemspec", "w") { |f| f << spec.to_ruby }
47
+ end
48
+
49
+ task :gem => ["saikuro_treemap.gemspec"] do
50
+ sh "gem build saikuro_treemap.gemspec"
51
+ end
52
+ end
53
+
54
+ task :test do
55
+ Rake::TestTask.new do |t|
56
+ t.libs << "test"
57
+ t.test_files = FileList['test/*test.rb']
58
+ t.verbose = true
59
+ end
60
+ end
61
+
62
+ task :default => :test
@@ -0,0 +1,76 @@
1
+ module SaikuroTreemap
2
+ class CCNNode
3
+ attr_reader :path
4
+
5
+ def initialize(path, complexity=0, lines=0)
6
+ @path = path
7
+ @children = []
8
+ @complexity = complexity
9
+ @lines = lines
10
+ end
11
+
12
+ def add_child(child)
13
+ @children << child
14
+ end
15
+
16
+ def find_node(*pathes)
17
+ return self if pathes.join("::") == @path
18
+ return self if pathes.empty?
19
+
20
+ # Eumerable#find is buggy for recursive calls!
21
+ @children.each do |child|
22
+ if r = child.find_node(pathes)
23
+ return r
24
+ end
25
+ end
26
+ return nil
27
+ end
28
+
29
+ def create_nodes(*pathes)
30
+ pathes.each_with_index {|_, i| find_or_create_node(*pathes[0..i]) }
31
+ end
32
+
33
+ def to_json(*args)
34
+ hash = { 'name' => compact_name, 'id' => @path, 'children' => @children }
35
+ data = {}
36
+ data['complexity'] = @complexity if @complexity != 0
37
+ data['lines'] = @lines if @lines != 0
38
+ data['$area'] = area if area != 0
39
+ data['$color'] = color if area != 0
40
+ hash['data'] = data
41
+ hash.to_json(*args)
42
+ end
43
+
44
+ def area
45
+ return @lines if leaf?
46
+ @children.inject(0) {|sum, child| sum + child.area }
47
+ end
48
+
49
+ def color
50
+ return "#101010" unless leaf?
51
+ return "#AE0000" if @complexity >= 10
52
+ return "#006500" if @complexity < 5
53
+ return "#4545C2"
54
+ end
55
+
56
+ def leaf?
57
+ return @children.empty?
58
+ end
59
+
60
+ private
61
+
62
+ def compact_name
63
+ return '' if @path !~ /\S/
64
+ @path.split('::').last.split('#').last
65
+ end
66
+
67
+ def find_or_create_node(*pathes)
68
+ find_node(*pathes) || create_node(*pathes)
69
+ end
70
+
71
+ def create_node(*pathes)
72
+ parent = find_node(*pathes[0..-2])
73
+ parent.add_child(CCNNode.new(pathes.join("::")))
74
+ end
75
+ end
76
+ end
@@ -0,0 +1,162 @@
1
+ # almost all of code in this file is from metric_fu
2
+ # http://metric-fu.rubyforge.org/
3
+ module SaikuroTreemap
4
+ module Parser
5
+ def self.parse(saikuro_out_dir)
6
+ files = Dir[File.join(saikuro_out_dir, "/**/*.html")].collect do |path|
7
+ SFile.new(path) if SFile.is_valid_text_file?(path)
8
+ end
9
+
10
+ files.compact!
11
+
12
+ files = files.sort_by do |file|
13
+ file.elements.
14
+ max {|a,b| a.complexity.to_i <=> b.complexity.to_i}.
15
+ complexity.to_i
16
+ end.reverse
17
+
18
+ files.each do |file|
19
+ file.elements.each do |element|
20
+ element.defs.each do |defn|
21
+ defn.name = "#{element.name}##{defn.name}"
22
+ end
23
+ end
24
+ end
25
+
26
+ files.collect(&:to_h)
27
+ end
28
+
29
+ class SFile
30
+ attr_reader :elements
31
+
32
+ def initialize(path)
33
+ @path = path
34
+ @file_handle = File.open(@path, "r")
35
+ @elements = []
36
+ get_elements
37
+ ensure
38
+ @file_handle.close if @file_handle
39
+ end
40
+
41
+ def self.is_valid_text_file?(path)
42
+ File.open(path, "r") do |f|
43
+ if f.eof? || !f.readline.match(/--/)
44
+ return false
45
+ else
46
+ return true
47
+ end
48
+ end
49
+ end
50
+
51
+ def filename
52
+ File.basename(@path, '_cyclo.html')
53
+ end
54
+
55
+ def to_h
56
+ merge_classes
57
+ {:classes => @elements}
58
+ end
59
+
60
+ def get_elements
61
+ begin
62
+ while (line = @file_handle.readline) do
63
+ return [] if line.nil? || line !~ /\S/
64
+ element ||= nil
65
+ if line.match /START/
66
+ unless element.nil?
67
+ @elements << element
68
+ element = nil
69
+ end
70
+ line = @file_handle.readline
71
+ element = ParsingElement.new(line)
72
+ elsif line.match /END/
73
+ @elements << element if element
74
+ element = nil
75
+ else
76
+ element << line if element
77
+ end
78
+ end
79
+ rescue EOFError
80
+ nil
81
+ end
82
+ end
83
+
84
+
85
+ def merge_classes
86
+ new_elements = []
87
+ get_class_names.each do |target_class|
88
+ elements = @elements.find_all {|el| el.name == target_class }
89
+ complexity = 0
90
+ lines = 0
91
+ defns = []
92
+ elements.each do |el|
93
+ complexity += el.complexity.to_i
94
+ lines += el.lines.to_i
95
+ defns << el.defs
96
+ end
97
+
98
+ new_element = {:class_name => target_class,
99
+ :complexity => complexity,
100
+ :lines => lines,
101
+ :methods => defns.flatten.map {|d| d.to_h}}
102
+ new_element[:methods] = new_element[:methods].
103
+ sort_by {|x| x[:complexity] }.
104
+ reverse
105
+
106
+ new_elements << new_element
107
+ end
108
+ @elements = new_elements if new_elements
109
+ end
110
+
111
+ def get_class_names
112
+ class_names = []
113
+ @elements.each do |element|
114
+ unless class_names.include?(element.name)
115
+ class_names << element.name
116
+ end
117
+ end
118
+ class_names
119
+ end
120
+ end
121
+
122
+
123
+
124
+ class ParsingElement
125
+ TYPE_REGEX=/Type:(.*) Name/
126
+ NAME_REGEX=/Name:(.*) Complexity/
127
+ COMPLEXITY_REGEX=/Complexity:(.*) Lines/
128
+ LINES_REGEX=/Lines:(.*)/
129
+
130
+ attr_reader :complexity, :lines, :defs, :element_type
131
+ attr_accessor :name
132
+
133
+ def initialize(line)
134
+ @line = line
135
+ @element_type = line.match(TYPE_REGEX)[1].strip
136
+ @name = line.match(NAME_REGEX)[1].strip
137
+ @complexity = line.match(COMPLEXITY_REGEX)[1].strip
138
+ @lines = line.match(LINES_REGEX)[1].strip
139
+ @defs = []
140
+ end
141
+
142
+ def <<(line)
143
+ @defs << ParsingElement.new(line)
144
+ end
145
+
146
+ def to_h
147
+ base = {:name => @name, :complexity => @complexity.to_i, :lines => @lines.to_i}
148
+ unless @defs.empty?
149
+ defs = @defs.map do |my_def|
150
+ my_def = my_def.to_h
151
+ my_def.delete(:defs)
152
+ my_def
153
+ end
154
+ base[:defs] = defs
155
+ end
156
+ return base
157
+ end
158
+ end
159
+ end
160
+
161
+
162
+ end
@@ -0,0 +1,74 @@
1
+ require 'rubygems'
2
+ require 'json'
3
+ require 'erb'
4
+ require 'rake'
5
+
6
+ require 'saikuro_treemap/parser'
7
+ require 'saikuro_treemap/ccn_node'
8
+
9
+ module SaikuroTreemap
10
+
11
+ DEFAULT_CONFIG = {
12
+ :code_dirs => ['app/controllers', 'app/models', 'app/helpers' 'lib'],
13
+ :output_file => 'reports/saikuro_treemap.html',
14
+ :saikuro_args => [
15
+ "--warn_cyclo 5",
16
+ "--error_cyclo 7",
17
+ "--formater text",
18
+ "--cyclo --filter_cyclo 0"]
19
+ }
20
+
21
+ def self.generate_treemap(config={})
22
+ temp_dir = 'tmp/saikuro'
23
+
24
+ config = DEFAULT_CONFIG.merge(config)
25
+
26
+ config[:saikuro_args] << "--output_directory #{temp_dir}"
27
+
28
+ options_string = config[:saikuro_args] + config[:code_dirs].collect { |dir| "--input_directory '#{dir}'" }
29
+
30
+
31
+ rm_rf temp_dir
32
+ sh %{saikuro #{options_string.join(' ')}} do |ok, response|
33
+ unless ok
34
+ puts "Saikuro failed with exit status: #{response.exitstatus}"
35
+ exit 1
36
+ end
37
+ end
38
+
39
+ saikuro_files = Parser.parse(temp_dir)
40
+
41
+ @ccn_node = create_ccn_root_node(saikuro_files)
42
+ FileUtils.mkdir_p(File.dirname(config[:output_file]))
43
+
44
+ File.open(config[:output_file], 'w') do |f|
45
+ f << ERB.new(File.read(template('saikuro.html.erb'))).result(binding)
46
+ end
47
+ end
48
+
49
+ def self.create_ccn_root_node(saikuro_files)
50
+ root_node = CCNNode.new('')
51
+ saikuro_files.each do |f|
52
+ f[:classes].each do |c|
53
+ class_node_name = c[:class_name]
54
+ namespaces = class_node_name.split("::")[0..-2]
55
+ root_node.create_nodes(*namespaces)
56
+ parent = root_node.find_node(*namespaces)
57
+ class_node = CCNNode.new(class_node_name, c[:complexity], c[:lines])
58
+ parent.add_child(class_node)
59
+
60
+ c[:methods].each do |m|
61
+ class_node.add_child(CCNNode.new(m[:name], m[:complexity], m[:lines]))
62
+ end
63
+ end
64
+ end
65
+
66
+ root_node
67
+ end
68
+
69
+ def self.template(file)
70
+ File.expand_path("../../templates/#{file}", __FILE__)
71
+ end
72
+
73
+ end
74
+
@@ -0,0 +1,37 @@
1
+ # -*- encoding: utf-8 -*-
2
+
3
+ Gem::Specification.new do |s|
4
+ s.name = %q{saikuro_treemap}
5
+ s.version = "0.1"
6
+
7
+ s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
8
+ s.authors = ["ThoughtWorks Studios"]
9
+ s.date = %q{2010-07-16}
10
+ s.description = %q{ Generate CCN Treemap based on saikuro analysis
11
+ }
12
+ s.email = %q{studios@thoughtworks.com}
13
+ s.extra_rdoc_files = ["README.textile"]
14
+ s.files = [".gitignore", "MIT-LICENSE.txt", "README.textile", "Rakefile", "lib/saikuro_treemap.rb", "lib/saikuro_treemap/ccn_node.rb", "lib/saikuro_treemap/parser.rb", "saikuro_treemap.gemspec", "templates/css/treemap.css", "templates/js/jit-min.js", "templates/js/jit.js", "templates/js/saikuro-render.js", "templates/saikuro.html.erb", "test/ccn_node_test.rb", "test/test_helper.rb"]
15
+ s.homepage = %q{http://github.com/ThoughtWorksStudios/saikuro_treemap}
16
+ s.require_paths = ["lib"]
17
+ s.rubyforge_project = %q{saikuro_treemap}
18
+ s.rubygems_version = %q{1.3.7}
19
+ s.summary = %q{Generate CCN Treemap based on saikuro analysis}
20
+ s.test_files = ["test/test_helper.rb"]
21
+
22
+ if s.respond_to? :specification_version then
23
+ current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
24
+ s.specification_version = 3
25
+
26
+ if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then
27
+ s.add_runtime_dependency(%q<json>, [">= 0"])
28
+ s.add_runtime_dependency(%q<Saikuro>, [">= 0"])
29
+ else
30
+ s.add_dependency(%q<json>, [">= 0"])
31
+ s.add_dependency(%q<Saikuro>, [">= 0"])
32
+ end
33
+ else
34
+ s.add_dependency(%q<json>, [">= 0"])
35
+ s.add_dependency(%q<Saikuro>, [">= 0"])
36
+ end
37
+ end
@@ -0,0 +1,33 @@
1
+ html, body {
2
+ margin:0;
3
+ padding:0;
4
+ font-family: "Lucida Grande", Verdana;
5
+ font-size: 0.9em;
6
+ background-color:#F2F2F2;
7
+ }
8
+
9
+ .node {
10
+ color:#FFFFFF;
11
+ overflow:hidden;
12
+ cursor:pointer;
13
+ }
14
+
15
+ #infovis {
16
+ text-align: center;
17
+ position:relative;
18
+ width:90%;
19
+ height:900px;
20
+ margin:auto;
21
+ overflow:hidden;
22
+ }
23
+
24
+ .tip {
25
+ color: #fff;
26
+ width: auto;
27
+ background-color: black;
28
+ opacity:0.9;
29
+ filter:alpha(opacity=90);
30
+ font-size:10px;
31
+ font-family:Verdana, Geneva, Arial, Helvetica, sans-serif;
32
+ padding:7px;
33
+ }