analyst 0.0.1 → 0.13.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.
- checksums.yaml +4 -4
- data/.gitignore +4 -1
- data/.rspec +2 -0
- data/CODE_OF_CONDUCT.md +13 -0
- data/README.md +1 -0
- data/analyst.gemspec +12 -0
- data/lib/analyst/analyzer.rb +162 -0
- data/lib/analyst/cli.rb +42 -0
- data/lib/analyst/entity_parser/association.rb +24 -0
- data/lib/analyst/entity_parser/entities/class.rb +92 -0
- data/lib/analyst/entity_parser/entities/empty.rb +13 -0
- data/lib/analyst/entity_parser/entities/entity.rb +29 -0
- data/lib/analyst/entity_parser/entities/method.rb +16 -0
- data/lib/analyst/entity_parser/entities/module.rb +31 -0
- data/lib/analyst/formatters/base.rb +33 -0
- data/lib/analyst/formatters/csv.rb +43 -0
- data/lib/analyst/formatters/html.rb +87 -0
- data/lib/analyst/formatters/html_index.rb +47 -0
- data/lib/analyst/formatters/templates/index.html.haml +92 -0
- data/lib/analyst/formatters/templates/output.html.haml +114 -0
- data/lib/analyst/formatters/text.rb +56 -0
- data/lib/analyst/fukuzatsu/analyzer.rb +162 -0
- data/lib/analyst/fukuzatsu/cli.rb +42 -0
- data/lib/analyst/fukuzatsu/entity_parser/association.rb +24 -0
- data/lib/analyst/fukuzatsu/entity_parser/entities/class.rb +92 -0
- data/lib/analyst/fukuzatsu/entity_parser/entities/empty.rb +13 -0
- data/lib/analyst/fukuzatsu/entity_parser/entities/entity.rb +29 -0
- data/lib/analyst/fukuzatsu/entity_parser/entities/method.rb +16 -0
- data/lib/analyst/fukuzatsu/entity_parser/entities/module.rb +31 -0
- data/lib/analyst/fukuzatsu/formatters/base.rb +33 -0
- data/lib/analyst/fukuzatsu/formatters/csv.rb +43 -0
- data/lib/analyst/fukuzatsu/formatters/html.rb +87 -0
- data/lib/analyst/fukuzatsu/formatters/html_index.rb +47 -0
- data/lib/analyst/fukuzatsu/formatters/templates/index.html.haml +92 -0
- data/lib/analyst/fukuzatsu/formatters/templates/output.html.haml +114 -0
- data/lib/analyst/fukuzatsu/formatters/text.rb +56 -0
- data/lib/analyst/fukuzatsu/line_of_code.rb +19 -0
- data/lib/analyst/fukuzatsu/parsed_file.rb +85 -0
- data/lib/analyst/fukuzatsu/parsed_method.rb +32 -0
- data/lib/analyst/fukuzatsu/parser_original.rb +76 -0
- data/lib/analyst/fukuzatsu/rethink/parser.rb +346 -0
- data/lib/analyst/fukuzatsu/version.rb +3 -0
- data/lib/analyst/line_of_code.rb +19 -0
- data/lib/analyst/parsed_file.rb +85 -0
- data/lib/analyst/parsed_method.rb +32 -0
- data/lib/analyst/parser.rb +76 -0
- data/lib/analyst/rethink/parser.rb +346 -0
- data/lib/analyst/version.rb +1 -1
- data/lib/analyst.rb +17 -2
- data/spec/analyzer_spec.rb +122 -0
- data/spec/cli_spec.rb +48 -0
- data/spec/fixtures/eg_class.rb +8 -0
- data/spec/fixtures/eg_mod_class.rb +2 -0
- data/spec/fixtures/eg_mod_class_2.rb +5 -0
- data/spec/fixtures/eg_module.rb +2 -0
- data/spec/fixtures/module_with_class.rb +9 -0
- data/spec/fixtures/multiple_methods.rb +7 -0
- data/spec/fixtures/nested_methods.rb +8 -0
- data/spec/fixtures/program_1.rb +19 -0
- data/spec/fixtures/program_2.rb +25 -0
- data/spec/fixtures/program_3.rb +66 -0
- data/spec/fixtures/program_4.rb +1 -0
- data/spec/fixtures/single_class.rb +9 -0
- data/spec/fixtures/single_method.rb +3 -0
- data/spec/formatters/csv_spec.rb +37 -0
- data/spec/formatters/html_index_spec.rb +36 -0
- data/spec/formatters/html_spec.rb +48 -0
- data/spec/formatters/text_spec.rb +39 -0
- data/spec/parsed_file_spec.rb +67 -0
- data/spec/parsed_method_spec.rb +34 -0
- data/spec/spec_helper.rb +7 -0
- metadata +229 -2
@@ -0,0 +1,31 @@
|
|
1
|
+
module Analyst
|
2
|
+
|
3
|
+
module EntityParser
|
4
|
+
module Entities
|
5
|
+
class Module < Entity
|
6
|
+
def name
|
7
|
+
const_node_array(ast.children.first).join('::')
|
8
|
+
end
|
9
|
+
def full_name
|
10
|
+
parent.full_name.empty? ? name : parent.full_name + '::' + name
|
11
|
+
end
|
12
|
+
|
13
|
+
private
|
14
|
+
|
15
|
+
# takes a (const) node and returns an array specifying the fully-qualified
|
16
|
+
# constant name that it represents. ya know, so CoolModule::SubMod::SweetClass
|
17
|
+
# would be parsed to:
|
18
|
+
# (const
|
19
|
+
# (const
|
20
|
+
# (const nil :CoolModule) :SubMod) :SweetClass)
|
21
|
+
# and passing that node here would return [:CoolModule, :SubMod, :SweetClass]
|
22
|
+
def const_node_array(node)
|
23
|
+
return [] if node.nil?
|
24
|
+
raise "expected (const) node or nil, got (#{node.type})" unless node.type == :const
|
25
|
+
const_node_array(node.children.first) << node.children[1]
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
module Formatters
|
2
|
+
|
3
|
+
module Base
|
4
|
+
|
5
|
+
def self.included(klass)
|
6
|
+
klass.send(:attr_accessor, :file)
|
7
|
+
klass.send(:attr_accessor, :source)
|
8
|
+
klass.send(:attr_accessor, :output_directory)
|
9
|
+
end
|
10
|
+
|
11
|
+
def initialize(file, output_directory=nil, source="")
|
12
|
+
self.file = file
|
13
|
+
self.source = source
|
14
|
+
self.output_directory = output_directory
|
15
|
+
end
|
16
|
+
|
17
|
+
def filename
|
18
|
+
File.basename(self.file.path_to_file) + file_extension
|
19
|
+
end
|
20
|
+
|
21
|
+
def output_path
|
22
|
+
output_path = File.dirname(File.join(self.output_directory, self.file.path_to_file))
|
23
|
+
FileUtils.mkpath(output_path)
|
24
|
+
output_path
|
25
|
+
end
|
26
|
+
|
27
|
+
def path_to_results
|
28
|
+
File.join(output_path, filename)
|
29
|
+
end
|
30
|
+
|
31
|
+
end
|
32
|
+
|
33
|
+
end
|
@@ -0,0 +1,43 @@
|
|
1
|
+
module Formatters
|
2
|
+
|
3
|
+
class Csv
|
4
|
+
|
5
|
+
include Formatters::Base
|
6
|
+
|
7
|
+
def self.has_index?
|
8
|
+
false
|
9
|
+
end
|
10
|
+
|
11
|
+
def self.writes_to_file_system?
|
12
|
+
true
|
13
|
+
end
|
14
|
+
|
15
|
+
def content
|
16
|
+
rows + "\r\n"
|
17
|
+
end
|
18
|
+
|
19
|
+
def export
|
20
|
+
begin
|
21
|
+
File.open(path_to_results, 'a') {|outfile| outfile.write(content)}
|
22
|
+
rescue Exception => e
|
23
|
+
puts "Unable to write output: #{e} #{e.backtrace}"
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
def file_extension
|
28
|
+
".csv"
|
29
|
+
end
|
30
|
+
|
31
|
+
def path_to_results
|
32
|
+
File.join(output_directory, "results#{file_extension}")
|
33
|
+
end
|
34
|
+
|
35
|
+
def rows
|
36
|
+
file.methods.map do |method|
|
37
|
+
"#{file.path_to_file},#{file.class_name},#{method.name},#{method.complexity}"
|
38
|
+
end.join("\r\n")
|
39
|
+
end
|
40
|
+
|
41
|
+
end
|
42
|
+
|
43
|
+
end
|
@@ -0,0 +1,87 @@
|
|
1
|
+
require 'rouge'
|
2
|
+
|
3
|
+
module Formatters
|
4
|
+
|
5
|
+
class Html
|
6
|
+
|
7
|
+
include Formatters::Base
|
8
|
+
|
9
|
+
def self.has_index?
|
10
|
+
true
|
11
|
+
end
|
12
|
+
|
13
|
+
def self.writes_to_file_system?
|
14
|
+
true
|
15
|
+
end
|
16
|
+
|
17
|
+
def self.index_class
|
18
|
+
Formatters::HtmlIndex
|
19
|
+
end
|
20
|
+
|
21
|
+
def columns
|
22
|
+
["class", "method", "complexity"]
|
23
|
+
end
|
24
|
+
|
25
|
+
def content
|
26
|
+
Haml::Engine.new(output_template).render(
|
27
|
+
Object.new, {
|
28
|
+
header: header,
|
29
|
+
rows: rows,
|
30
|
+
source_lines: preprocessed,
|
31
|
+
class_name: file.class_name,
|
32
|
+
complexity: file.complexity,
|
33
|
+
path_to_file: file.path_to_file,
|
34
|
+
date: Time.now.strftime("%Y/%m/%d"),
|
35
|
+
time: Time.now.strftime("%l:%M %P")
|
36
|
+
}
|
37
|
+
)
|
38
|
+
end
|
39
|
+
|
40
|
+
def export
|
41
|
+
begin
|
42
|
+
File.open(path_to_results, 'w') {|outfile| outfile.write(content)}
|
43
|
+
rescue Exception => e
|
44
|
+
puts "Unable to write output: #{e} #{e.backtrace}"
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
def formatter
|
49
|
+
Rouge::Formatters::HTML.new(line_numbers: true)
|
50
|
+
end
|
51
|
+
|
52
|
+
def header
|
53
|
+
columns.map{|col| "<th>#{col.titleize}</th>"}.join("\r\n")
|
54
|
+
end
|
55
|
+
|
56
|
+
def lexer
|
57
|
+
lexer = Rouge::Lexers::Ruby.new
|
58
|
+
end
|
59
|
+
|
60
|
+
def output_template
|
61
|
+
File.read(File.dirname(__FILE__) + "/templates/output.html.haml")
|
62
|
+
end
|
63
|
+
|
64
|
+
def preprocessed
|
65
|
+
formatter.format(lexer.lex(source))
|
66
|
+
end
|
67
|
+
|
68
|
+
def rows
|
69
|
+
i = 0
|
70
|
+
file.methods.inject([]) do |a, method|
|
71
|
+
i += 1
|
72
|
+
a << "<tr class='#{i % 2 == 1 ? 'even' : 'odd'}'>"
|
73
|
+
a << " <td>#{file.class_name}</td>"
|
74
|
+
a << " <td>#{method.name}</td>"
|
75
|
+
a << " <td>#{method.complexity}</td>"
|
76
|
+
a << "</tr>"
|
77
|
+
a
|
78
|
+
end.join("\r\n")
|
79
|
+
end
|
80
|
+
|
81
|
+
def file_extension
|
82
|
+
".htm"
|
83
|
+
end
|
84
|
+
|
85
|
+
end
|
86
|
+
|
87
|
+
end
|
@@ -0,0 +1,47 @@
|
|
1
|
+
module Formatters
|
2
|
+
|
3
|
+
class HtmlIndex
|
4
|
+
|
5
|
+
include Formatters::Base
|
6
|
+
|
7
|
+
attr_reader :file_summary, :output_directory
|
8
|
+
|
9
|
+
def initialize(file_summary, output_directory=nil)
|
10
|
+
@file_summary = file_summary
|
11
|
+
@output_directory = output_directory
|
12
|
+
end
|
13
|
+
|
14
|
+
def content
|
15
|
+
Haml::Engine.new(output_template).render(
|
16
|
+
Object.new, {
|
17
|
+
file_summary: file_summary,
|
18
|
+
date: Time.now.strftime("%Y/%m/%d"),
|
19
|
+
time: Time.now.strftime("%l:%M %P")
|
20
|
+
}
|
21
|
+
)
|
22
|
+
end
|
23
|
+
|
24
|
+
def export
|
25
|
+
begin
|
26
|
+
File.open(path_to_results, 'w') {|outfile| outfile.write(content)}
|
27
|
+
rescue Exception => e
|
28
|
+
puts "Unable to write output: #{e} #{e.backtrace}"
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
def filename
|
33
|
+
"index.htm"
|
34
|
+
end
|
35
|
+
|
36
|
+
def output_path
|
37
|
+
FileUtils.mkpath(self.output_directory)
|
38
|
+
self.output_directory
|
39
|
+
end
|
40
|
+
|
41
|
+
def output_template
|
42
|
+
File.read(File.dirname(__FILE__) + "/templates/index.html.haml")
|
43
|
+
end
|
44
|
+
|
45
|
+
end
|
46
|
+
|
47
|
+
end
|
@@ -0,0 +1,92 @@
|
|
1
|
+
!!!
|
2
|
+
%html
|
3
|
+
%head
|
4
|
+
%title
|
5
|
+
Analyst
|
6
|
+
%link{href: "http://cdn.datatables.net/1.10.0/css/jquery.dataTables.css", rel: "stylesheet"}
|
7
|
+
%script{language: "javascript", src: "http://code.jquery.com/jquery-1.11.0.min.js", type: "text/javascript"}
|
8
|
+
%script{language: "javascript", src: "http://code.jquery.com/jquery-migrate-1.2.1.min.js", type: "text/javascript"}
|
9
|
+
%script{language: "javascript", src: "http://cdn.datatables.net/1.10.0/js/jquery.dataTables.js", type: "text/javascript"}
|
10
|
+
|
11
|
+
%style{media: "screen", type: "text/css"}
|
12
|
+
body { background: #593232; color: #fff; font-family: arial, sans-serif; padding: 2em; }
|
13
|
+
table { box-shadow: 0 5px 0 rgba(0,0,0,.8); background: #444; border-spacing: 0; border: 5px solid #000; border-radius: 25px; border-collapse: collapse; min-width: 50%; }
|
14
|
+
th.sorting_asc, th.sorting_desc { text-transform: uppercase !important; font-size: .8em !important; background-image: none !important; background: rgba(64, 41, 41, .5) !important;}
|
15
|
+
th.sorting { background-position: left !important; border-right: 1px solid #222; text-transform: uppercase !important; font-size: .8em !important}
|
16
|
+
tr.header th:first-child { border-radius: 6px 0 0 0; }
|
17
|
+
tr.header th:last-child { border-radius: 0 6px 0 0; }
|
18
|
+
tr.header th:only-child { border-radius: 6px 6px 0 0; }
|
19
|
+
tr.header { background-color: #222; }
|
20
|
+
tr.even { background: rgba(128, 128, 128, 0.5) !important;}
|
21
|
+
tr.odd { background: rgba(128, 128, 128, 0.25) !important}
|
22
|
+
tr.even:hover, tr.odd:hover { background: rgba(128, 128, 128, 0.75) !important;}
|
23
|
+
th { background: #000; text-align: left; padding: .5em; }
|
24
|
+
td { text-align: left; padding: .5em; padding-left: 1.25em !important;}
|
25
|
+
td.center { text-align: center; }
|
26
|
+
td.sorting_1 { background: none !important; padding-left: 1.25em !important; }
|
27
|
+
tfoot { background: #000; border-top: 10px solid #000; font-family: courier; margin-top: 4em; font-size: .75em; }
|
28
|
+
a:link, a:visited { color: #aaa }
|
29
|
+
div.file_meta { float: left; height: 3em; width: 30%; }
|
30
|
+
h1 { color:#fff; font-size: 1.25em; margin-top: .25em; }
|
31
|
+
h2 { color:#fff; font-size: .75em; margin-top: -1em; }
|
32
|
+
h3 { color:#fff; font-size: 1em; float: right; margin-top: 1em; }
|
33
|
+
td.sorting_1 { background: none !important; padding-left: 1.25em !important; }
|
34
|
+
div.dataTables_filter { margin-bottom: 2em !important; }
|
35
|
+
div.dataTables_filter label { color: #fff; }
|
36
|
+
div.dataTables_paginate { display: none !important; }
|
37
|
+
div.dataTables_info { display: none !important; }
|
38
|
+
|
39
|
+
%body
|
40
|
+
%table{class: "output-table"}
|
41
|
+
%thead
|
42
|
+
%tr.header
|
43
|
+
%th{colspan: 4}
|
44
|
+
.file_meta
|
45
|
+
%h1
|
46
|
+
Project Overview
|
47
|
+
%tr
|
48
|
+
%th
|
49
|
+
File
|
50
|
+
%th
|
51
|
+
Module/Class Name
|
52
|
+
%th
|
53
|
+
Complexity
|
54
|
+
%th
|
55
|
+
Details
|
56
|
+
%tbody
|
57
|
+
- file_summary.each do |summary|
|
58
|
+
%tr
|
59
|
+
%td
|
60
|
+
%a{href: "#{summary[:path_to_file]}.htm"}
|
61
|
+
= summary[:path_to_file]
|
62
|
+
%td
|
63
|
+
- if summary[:class_name].size > 25
|
64
|
+
= "..."
|
65
|
+
= summary[:class_name][-24..-1]
|
66
|
+
- else
|
67
|
+
= summary[:class_name]
|
68
|
+
%td
|
69
|
+
= summary[:complexity]
|
70
|
+
%td
|
71
|
+
%a{href: "#{summary[:path_to_file]}.htm"}
|
72
|
+
View Details
|
73
|
+
%tfoot
|
74
|
+
%tr
|
75
|
+
%td.center{colspan: 4}
|
76
|
+
%em
|
77
|
+
Analyzed on
|
78
|
+
= date
|
79
|
+
at
|
80
|
+
= time
|
81
|
+
by
|
82
|
+
%a{href: "https://gitlab.com/coraline/Analyst", target: "_new"}
|
83
|
+
Analyst
|
84
|
+
:javascript
|
85
|
+
$(document).ready(function(){
|
86
|
+
$('.output-table').dataTable({
|
87
|
+
bLengthChange: false,
|
88
|
+
iDisplayLength: 25000,
|
89
|
+
"order": [[2, "desc"]]
|
90
|
+
});
|
91
|
+
});
|
92
|
+
|
@@ -0,0 +1,114 @@
|
|
1
|
+
!!!
|
2
|
+
%html
|
3
|
+
%head
|
4
|
+
%title
|
5
|
+
Analyst:
|
6
|
+
= class_name
|
7
|
+
%link{href: "http://cdn.datatables.net/1.10.0/css/jquery.dataTables.css", rel: "stylesheet"}
|
8
|
+
%script{language: "javascript", src: "http://code.jquery.com/jquery-1.11.0.min.js", type: "text/javascript"}
|
9
|
+
%script{language: "javascript", src: "http://code.jquery.com/jquery-migrate-1.2.1.min.js", type: "text/javascript"}
|
10
|
+
%script{language: "javascript", src: "http://cdn.datatables.net/1.10.0/js/jquery.dataTables.js", type: "text/javascript"}
|
11
|
+
%script{language: "javascript", src: "http://cdnjs.cloudflare.com/ajax/libs/highlight.js/8.0/highlight.min.js", type: "text/javascript"}
|
12
|
+
:css
|
13
|
+
#{Rouge::Theme.find('thankful_eyes').render(scope: '.highlight')}
|
14
|
+
body { background: #593232; color: #fff; font-family: arial, sans-serif; padding: 2em; }
|
15
|
+
div.column { float: left; width: 45%; margin-left: 4%; }
|
16
|
+
div.file_listing { padding: .1em; border-radius: 5px; background: #000; width: 100%; border: 1px solid #000;}
|
17
|
+
div.file_meta { float: left; height: 3em; width: 30%; }
|
18
|
+
h1 { color:#fff; font-size: 1.25em; margin-top: .25em; }
|
19
|
+
h2 { color:#fff; font-size: .75em; margin-top: -1em; }
|
20
|
+
h3 { color:#fff; font-size: 1.1em;margin-top: 1em; }
|
21
|
+
h3.highlighted { background: rgba(170, 161, 57, .6); border-radius: 100px; padding: .25em; padding-left: 1em; color: #000;}
|
22
|
+
h3.highlighted-method { background: rgba(153, 51, 80, .6); border-radius: 100px; padding-left: 1em; }
|
23
|
+
li { margin-bottom: 1em;}
|
24
|
+
pre { line-height: 1.75em;}
|
25
|
+
pre.lineno { margin-top: -1.4em !important;}
|
26
|
+
span.highlighted { padding-left: 1em; display: inline-block; position: absolute; left: 0px; padding-right: 90%}
|
27
|
+
a:link, a:visited { color: #aaa }
|
28
|
+
|
29
|
+
.file_listing table { width: 100%; box-shadow: 0 5px 0 rgba(0,0,0,.8); border-spacing: 0; border: 5px solid #000; border-radius: 5px; border-collapse: collapse; min-width: 50%; }
|
30
|
+
.file_listing td { text-align: left; padding: .5em; padding-left: 1.25em !important;}
|
31
|
+
.file_listing td.gutter { background-color: rgb(109, 109, 109) !important; }
|
32
|
+
.file_listing tfoot { background: #000; border-top: 10px solid #000; font-family: courier; margin-top: 4em; font-size: .75em; }
|
33
|
+
.file_listing th { background: #000; text-align: left; padding: .5em; }
|
34
|
+
.file_listing tr.faint td { opacity: 0.5; font-style: italic; }
|
35
|
+
.file_listing tr.header { background-color: #222; }
|
36
|
+
.file_listing tr { background: rgba(0, 0, 0, 0.25) !important; border-bottom: 1px solid #000;}
|
37
|
+
.file_listing th { background: #000; text-align: left; padding: .5em;}
|
38
|
+
.file_listing td { text-align: left; padding: .5em; padding-left: 1.25em !important;}
|
39
|
+
.file_listing td.center { text-align: center; }
|
40
|
+
.file_listing td.sorting_1 { background: none !important; padding-left: 1.25em !important; }
|
41
|
+
.file_listing tfoot { background: #000; font-family: courier; margin-top: 4em; font-size: .75em; }
|
42
|
+
|
43
|
+
table { box-shadow: 0 5px 0 rgba(0,0,0,.8); background: #444; border-spacing: 0; border: 5px solid #000; border-radius: 25px; border-collapse: collapse; min-width: 50%; }
|
44
|
+
th.sorting_asc, th.sorting_desc { text-transform: uppercase !important; font-size: .8em !important; background-image: none !important; background: rgba(64, 41, 41, .5) !important;}
|
45
|
+
th.sorting { background-position: left !important; border-right: 1px solid #222; text-transform: uppercase !important; font-size: .8em !important}
|
46
|
+
tr.header th:first-child { border-radius: 6px 0 0 0; }
|
47
|
+
tr.header th:last-child { border-radius: 0 6px 0 0; }
|
48
|
+
tr.header th:only-child { border-radius: 6px 6px 0 0; }
|
49
|
+
tfoot tr { border-radius: 6px 6px 6px 6px; }
|
50
|
+
tr.header { background-color: #222; }
|
51
|
+
tr.even { background: rgba(128, 128, 128, 0.5) !important;}
|
52
|
+
tr.odd { background: rgba(128, 128, 128, 0.25) !important}
|
53
|
+
tr.even:hover, tr.odd:hover { background: rgba(128, 128, 128, 0.75) !important;}
|
54
|
+
th { background: #000; text-align: left; padding: .5em;}
|
55
|
+
td { text-align: left; padding: .5em; padding-left: 1.25em !important;}
|
56
|
+
td.center { text-align: center; }
|
57
|
+
td.sorting_1 { background: none !important; padding-left: 1.25em !important; }
|
58
|
+
tfoot { background: #000; font-family: courier; margin-top: 4em; font-size: .75em; }
|
59
|
+
|
60
|
+
h1 { color:#fff; font-size: 1.25em; margin-top: .25em; }
|
61
|
+
h2 { color:#fff; font-size: .75em; margin-top: -1em; }
|
62
|
+
h3 { color:#fff; font-size: 1em; float: right; margin-top: 1em; }
|
63
|
+
|
64
|
+
div.dataTables_filter { display: none !important; }
|
65
|
+
div.dataTables_paginate { display: none !important; }
|
66
|
+
div.dataTables_info { display: none !important; }
|
67
|
+
|
68
|
+
|
69
|
+
%body
|
70
|
+
.container
|
71
|
+
%table{class: "output-table"}
|
72
|
+
%thead
|
73
|
+
%tr.header
|
74
|
+
%th{colspan: 3}
|
75
|
+
.file_meta
|
76
|
+
%h1
|
77
|
+
= class_name
|
78
|
+
%h2
|
79
|
+
= path_to_file
|
80
|
+
.file_meta_right
|
81
|
+
%h3
|
82
|
+
= "Overall Complexity: #{complexity}"
|
83
|
+
%tr
|
84
|
+
= header
|
85
|
+
%tbody
|
86
|
+
= rows
|
87
|
+
%tfoot
|
88
|
+
%tr
|
89
|
+
%td.center{colspan: 3}
|
90
|
+
%em
|
91
|
+
Analyzed on
|
92
|
+
= date
|
93
|
+
at
|
94
|
+
= time
|
95
|
+
by
|
96
|
+
%a{href: "https://gitlab.com/coraline/Analyst", target: "_new"}
|
97
|
+
Analyst
|
98
|
+
%br
|
99
|
+
%input{onclick: "history.back(-1)", type: "button", value: "Back"}
|
100
|
+
|
101
|
+
%br
|
102
|
+
|
103
|
+
.file_listing
|
104
|
+
= source_lines
|
105
|
+
|
106
|
+
:javascript
|
107
|
+
$(document).ready(function(){
|
108
|
+
$('.output-table').dataTable({
|
109
|
+
bLengthChange: false,
|
110
|
+
iDisplayLength: 25000,
|
111
|
+
"order": [[2, "desc"]]
|
112
|
+
});
|
113
|
+
});
|
114
|
+
|
@@ -0,0 +1,56 @@
|
|
1
|
+
module Formatters
|
2
|
+
|
3
|
+
require 'terminal-table'
|
4
|
+
require 'rainbow/ext/string'
|
5
|
+
|
6
|
+
class Text
|
7
|
+
|
8
|
+
include Formatters::Base
|
9
|
+
|
10
|
+
def self.has_index?
|
11
|
+
false
|
12
|
+
end
|
13
|
+
|
14
|
+
def self.writes_to_file_system?
|
15
|
+
false
|
16
|
+
end
|
17
|
+
|
18
|
+
def color_for(row)
|
19
|
+
return :green if row.complexity == 0
|
20
|
+
return :yellow if row.complexity <= file.average_complexity
|
21
|
+
return :red if row.complexity > file.average_complexity
|
22
|
+
return :white
|
23
|
+
end
|
24
|
+
|
25
|
+
def header
|
26
|
+
["Class/Module", "Method", "Complexity"]
|
27
|
+
end
|
28
|
+
|
29
|
+
def export
|
30
|
+
return if rows.empty?
|
31
|
+
table = Terminal::Table.new(
|
32
|
+
title: file.path_to_file.color(:white),
|
33
|
+
headings: header,
|
34
|
+
rows: rows,
|
35
|
+
style: {width: 90}
|
36
|
+
)
|
37
|
+
table.align_column(3, :right)
|
38
|
+
puts table
|
39
|
+
end
|
40
|
+
|
41
|
+
def rows
|
42
|
+
file.methods.map do |method|
|
43
|
+
color = color_for(method)
|
44
|
+
[wrap("#{file.class_name}").color(color), wrap("#{method.name}".color(color)), "#{method.complexity}".color(color)]
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
def wrap(string)
|
49
|
+
return string if string.length < 25
|
50
|
+
string[0..20] << "..."
|
51
|
+
end
|
52
|
+
|
53
|
+
end
|
54
|
+
|
55
|
+
end
|
56
|
+
|
@@ -0,0 +1,19 @@
|
|
1
|
+
class LineOfCode
|
2
|
+
|
3
|
+
include PoroPlus
|
4
|
+
include Ephemeral::Base
|
5
|
+
|
6
|
+
attr_accessor :line_number, :range, :content
|
7
|
+
|
8
|
+
def self.containing(locs, start_index, end_index)
|
9
|
+
locs.inject([]) do |a, loc|
|
10
|
+
a << loc if loc.in_range?(start_index) || loc.in_range?(end_index)
|
11
|
+
a
|
12
|
+
end.compact
|
13
|
+
end
|
14
|
+
|
15
|
+
def in_range?(index)
|
16
|
+
self.range.include?(index)
|
17
|
+
end
|
18
|
+
|
19
|
+
end
|
@@ -0,0 +1,85 @@
|
|
1
|
+
class ParsedFile
|
2
|
+
|
3
|
+
attr_accessor :lines_of_code, :source
|
4
|
+
attr_accessor :complexity, :path_to_file, :class_name, :path_to_results
|
5
|
+
|
6
|
+
def initialize(path_to_file: path_to_file, class_name: class_name=nil, complexity: complexity)
|
7
|
+
@path_to_file = path_to_file
|
8
|
+
@class_name = class_name
|
9
|
+
@lines_of_code = []
|
10
|
+
@complexity = complexity
|
11
|
+
@source = parse!
|
12
|
+
end
|
13
|
+
|
14
|
+
def class_name
|
15
|
+
@class_name ||= analyzer.class_name
|
16
|
+
end
|
17
|
+
|
18
|
+
def class_references
|
19
|
+
@class_references ||= analyzer.constants
|
20
|
+
end
|
21
|
+
|
22
|
+
def average_complexity
|
23
|
+
methods.map(&:complexity).reduce(:+) / methods.count.to_f
|
24
|
+
end
|
25
|
+
|
26
|
+
def complexity
|
27
|
+
@complexity ||= analyzer.complexity
|
28
|
+
end
|
29
|
+
|
30
|
+
def methods
|
31
|
+
@methods ||= analyzer.methods
|
32
|
+
end
|
33
|
+
|
34
|
+
def method_counts
|
35
|
+
referenced_methods = methods.map(&:references).flatten
|
36
|
+
referenced_methods.inject({}) do |hash, method|
|
37
|
+
hash[method] ||= 0
|
38
|
+
hash[method] += 1
|
39
|
+
hash
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
def source
|
44
|
+
return @source if @source
|
45
|
+
end_pos = 0
|
46
|
+
self.lines_of_code = []
|
47
|
+
@source = File.readlines(self.path_to_file).each_with_index do |line, index|
|
48
|
+
start_pos = end_pos + 1
|
49
|
+
end_pos += line.size
|
50
|
+
self.lines_of_code << LineOfCode.new(line_number: index + 1, range: (start_pos..end_pos))
|
51
|
+
line
|
52
|
+
end.join
|
53
|
+
end
|
54
|
+
|
55
|
+
def summary
|
56
|
+
{
|
57
|
+
path_to_file: self.path_to_file,
|
58
|
+
results_file: self.path_to_results,
|
59
|
+
source: source,
|
60
|
+
class_name: self.class_name,
|
61
|
+
complexity: complexity
|
62
|
+
}
|
63
|
+
end
|
64
|
+
|
65
|
+
private
|
66
|
+
|
67
|
+
def analyzer
|
68
|
+
@analyzer ||= Analyzer.new(content)
|
69
|
+
end
|
70
|
+
|
71
|
+
def content
|
72
|
+
@content ||= File.open(path_to_file, "r").read
|
73
|
+
end
|
74
|
+
|
75
|
+
def parse!
|
76
|
+
end_pos = 0
|
77
|
+
File.readlines(self.path_to_file).each_with_index do |line, index|
|
78
|
+
start_pos = end_pos + 1
|
79
|
+
end_pos += line.size
|
80
|
+
self.lines_of_code << LineOfCode.new(line_number: index + 1, range: (start_pos..end_pos))
|
81
|
+
line
|
82
|
+
end.join
|
83
|
+
end
|
84
|
+
|
85
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
class ParsedMethod
|
2
|
+
|
3
|
+
attr_reader :name, :content, :type, :complexity, :references
|
4
|
+
|
5
|
+
def initialize(name: name, content: content, type: type, refs: refs=[], complexity: complexity)
|
6
|
+
@name = name
|
7
|
+
@content = content
|
8
|
+
@type = type
|
9
|
+
@references = refs
|
10
|
+
@complexity = complexity
|
11
|
+
end
|
12
|
+
|
13
|
+
def complexity
|
14
|
+
@complexity ||= analyzer.complexity
|
15
|
+
end
|
16
|
+
|
17
|
+
def name
|
18
|
+
return "" if self.type == :none
|
19
|
+
"#{prefix}#{@name}"
|
20
|
+
end
|
21
|
+
|
22
|
+
def prefix
|
23
|
+
return "." if self.type == :class
|
24
|
+
return "#" if self.type == :instance
|
25
|
+
return "*"
|
26
|
+
end
|
27
|
+
|
28
|
+
def analyzer
|
29
|
+
Analyzer.new(self.content)
|
30
|
+
end
|
31
|
+
|
32
|
+
end
|