saikuro_treemap 0.1

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/.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
+ }