fukuzatsu 1.0.6 → 2.1.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.
Files changed (55) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +1 -1
  3. data/README.md +11 -9
  4. data/doc/images/details.png +0 -0
  5. data/doc/images/overview.png +0 -0
  6. data/fukuzatsu.gemspec +5 -2
  7. data/lib/fukuzatsu.rb +18 -4
  8. data/lib/fukuzatsu/cli.rb +2 -15
  9. data/lib/fukuzatsu/file_reader.rb +38 -0
  10. data/lib/fukuzatsu/formatters/base.rb +61 -22
  11. data/lib/fukuzatsu/formatters/csv.rb +28 -29
  12. data/lib/fukuzatsu/formatters/html.rb +61 -66
  13. data/lib/fukuzatsu/formatters/html_index.rb +40 -33
  14. data/lib/fukuzatsu/formatters/json.rb +37 -0
  15. data/lib/fukuzatsu/formatters/json_index.rb +31 -0
  16. data/lib/fukuzatsu/formatters/templates/index.html.haml +21 -32
  17. data/lib/fukuzatsu/formatters/templates/output.html.haml +19 -50
  18. data/lib/fukuzatsu/formatters/text.rb +53 -41
  19. data/lib/fukuzatsu/parser.rb +31 -49
  20. data/lib/fukuzatsu/summary.rb +101 -0
  21. data/lib/fukuzatsu/version.rb +1 -1
  22. data/spec/cli_spec.rb +7 -40
  23. data/spec/fixtures/class.rb +30 -0
  24. data/spec/fixtures/module.rb +12 -0
  25. data/spec/fixtures/procedural.rb +8 -0
  26. data/spec/formatters/csv_spec.rb +43 -21
  27. data/spec/formatters/html_spec.rb +38 -19
  28. data/spec/formatters/json_spec.rb +81 -0
  29. data/spec/formatters/text_spec.rb +18 -18
  30. data/spec/parser_spec.rb +39 -0
  31. data/spec/summary_spec.rb +28 -0
  32. metadata +51 -43
  33. data/doc/details.png +0 -0
  34. data/doc/overview.png +0 -0
  35. data/lib/fukuzatsu/analyzer.rb +0 -162
  36. data/lib/fukuzatsu/line_of_code.rb +0 -19
  37. data/lib/fukuzatsu/parsed_file.rb +0 -89
  38. data/lib/fukuzatsu/parsed_method.rb +0 -32
  39. data/spec/analyzer_spec.rb +0 -122
  40. data/spec/fixtures/eg_class.rb +0 -8
  41. data/spec/fixtures/eg_mod_class.rb +0 -2
  42. data/spec/fixtures/eg_mod_class_2.rb +0 -5
  43. data/spec/fixtures/eg_module.rb +0 -2
  44. data/spec/fixtures/module_with_class.rb +0 -9
  45. data/spec/fixtures/multiple_methods.rb +0 -7
  46. data/spec/fixtures/nested_methods.rb +0 -8
  47. data/spec/fixtures/program_1.rb +0 -19
  48. data/spec/fixtures/program_2.rb +0 -25
  49. data/spec/fixtures/program_3.rb +0 -66
  50. data/spec/fixtures/program_4.rb +0 -1
  51. data/spec/fixtures/single_class.rb +0 -9
  52. data/spec/fixtures/single_method.rb +0 -3
  53. data/spec/formatters/html_index_spec.rb +0 -36
  54. data/spec/parsed_file_spec.rb +0 -67
  55. data/spec/parsed_method_spec.rb +0 -34
@@ -1,47 +1,54 @@
1
- module Formatters
1
+ module Fukuzatsu
2
2
 
3
- class HtmlIndex
3
+ module Formatters
4
4
 
5
- include Formatters::Base
5
+ class HtmlIndex
6
6
 
7
- attr_reader :file_summary, :output_directory
7
+ include Formatters::Base
8
8
 
9
- def initialize(file_summary, output_directory=nil)
10
- @file_summary = file_summary
11
- @output_directory = output_directory
12
- end
9
+ attr_reader :summaries
13
10
 
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
11
+ def initialize(summaries)
12
+ @summaries = summaries
13
+ end
23
14
 
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}"
15
+ def content
16
+ Haml::Engine.new(output_template).render(
17
+ Object.new, {
18
+ summaries: summaries,
19
+ date: Time.now.strftime("%Y/%m/%d"),
20
+ time: Time.now.strftime("%l:%M %P")
21
+ }
22
+ )
29
23
  end
30
- end
31
24
 
32
- def filename
33
- "index.htm"
34
- end
25
+ def export
26
+ begin
27
+ File.open(path_to_results, 'w') {|outfile| outfile.write(content)}
28
+ rescue Exception => e
29
+ puts "Unable to write output: #{e} #{e.backtrace}"
30
+ end
31
+ end
35
32
 
36
- def output_path
37
- FileUtils.mkpath(self.output_directory)
38
- self.output_directory
39
- end
33
+ def filename
34
+ "index.htm"
35
+ end
36
+
37
+ def file_extension
38
+ ".htm"
39
+ end
40
+
41
+ def output_path
42
+ FileUtils.mkpath(self.output_directory)
43
+ self.output_directory
44
+ end
45
+
46
+ def output_template
47
+ File.read(File.dirname(__FILE__) + "/templates/index.html.haml")
48
+ end
40
49
 
41
- def output_template
42
- File.read(File.dirname(__FILE__) + "/templates/index.html.haml")
43
50
  end
44
51
 
45
52
  end
46
53
 
47
- end
54
+ end
@@ -0,0 +1,37 @@
1
+ module Fukuzatsu
2
+
3
+ module Formatters
4
+
5
+ class Json
6
+
7
+ include Formatters::Base
8
+
9
+ def self.index(summaries)
10
+ Fukuzatsu::Formatters::JsonIndex.new(summaries).export
11
+ end
12
+
13
+ def as_json
14
+ result = {
15
+ source_file: summary.source_file,
16
+ object: summary.container_name,
17
+ name: summary.entity_name,
18
+ complexity: summary.complexity
19
+ }
20
+ if summary.is_class_or_module?
21
+ result[:average_complexity] = summary.average_complexity
22
+ result[:methods] = summary.summaries.map { |s| Json.new(summary: s).as_json }
23
+ end
24
+ result
25
+ end
26
+
27
+ def content
28
+ as_json.to_json
29
+ end
30
+
31
+ def file_extension
32
+ ".json"
33
+ end
34
+ end
35
+
36
+ end
37
+ end
@@ -0,0 +1,31 @@
1
+ module Fukuzatsu
2
+
3
+ module Formatters
4
+
5
+ class JsonIndex
6
+
7
+ include Formatters::Base
8
+
9
+ attr_reader :summaries
10
+
11
+ def initialize(summaries)
12
+ @summaries = summaries
13
+ end
14
+
15
+ def content
16
+ summaries.map { |summary| Json.new(summary: summary).as_json }.to_json
17
+ end
18
+
19
+ def filename
20
+ "json/results.json"
21
+ end
22
+
23
+ def file_extension
24
+ ".json"
25
+ end
26
+
27
+ end
28
+
29
+ end
30
+
31
+ end
@@ -9,30 +9,19 @@
9
9
  %script{language: "javascript", src: "http://cdn.datatables.net/1.10.0/js/jquery.dataTables.js", type: "text/javascript"}
10
10
 
11
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; }
12
+ body { font-family: Arial, sans-serif; font-size: 13px; padding: 2em; }
13
+
14
+ a { color: #900; }
15
+ table { border: 4px solid #444; width: 100%; }
16
+
17
+ tr.sort-controls th { background-color: #999 !important; font-size: .75em; text-transform: uppercase; text-align: left; }
18
+
19
+ tr.header th, tfoot { background-color: #444 !important; color: #fff; }
20
+ tfoot td { font-size: .75em; }
21
+ tr.odd td { background-color: #eee; }
22
+
34
23
  div.dataTables_filter { margin-bottom: 2em !important; }
35
- div.dataTables_filter label { color: #fff; }
24
+ div.dataTables_filter label { color: ::fff; }
36
25
  div.dataTables_paginate { display: none !important; }
37
26
  div.dataTables_info { display: none !important; }
38
27
 
@@ -44,7 +33,7 @@
44
33
  .file_meta
45
34
  %h1
46
35
  Project Overview
47
- %tr
36
+ %tr.sort-controls
48
37
  %th
49
38
  File
50
39
  %th
@@ -54,21 +43,21 @@
54
43
  %th
55
44
  Details
56
45
  %tbody
57
- - file_summary.each do |summary|
46
+ - summaries.each do |summary|
58
47
  %tr
59
48
  %td
60
- %a{href: "#{summary[:path_to_file]}.htm"}
61
- = summary[:path_to_file]
49
+ %a{href: "#{summary.source_file}.htm"}
50
+ = summary.source_file
62
51
  %td
63
- - if summary[:class_name].size > 25
52
+ - if summary.entity.full_name.size > 50
64
53
  = "..."
65
- = summary[:class_name][-24..-1]
54
+ = summary.entity.full_name[-49..-1]
66
55
  - else
67
- = summary[:class_name]
56
+ = summary.entity.full_name
68
57
  %td
69
- = summary[:complexity]
58
+ = sprintf("%0.2f", summary.average_complexity)
70
59
  %td
71
- %a{href: "#{summary[:path_to_file]}.htm"}
60
+ %a{href: "#{summary.source_file}.htm"}
72
61
  View Details
73
62
  %tfoot
74
63
  %tr
@@ -11,61 +11,30 @@
11
11
  %script{language: "javascript", src: "http://cdnjs.cloudflare.com/ajax/libs/highlight.js/8.0/highlight.min.js", type: "text/javascript"}
12
12
  :css
13
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
14
 
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; }
15
+ body {
16
+ font-family: Arial, sans-serif;
17
+ font-size: 13px;
18
+ padding: 2em;
19
+ }
42
20
 
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; }
21
+ a { color: #fff; }
22
+ table { border: 4px solid #444; width: 100%; }
59
23
 
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; }
24
+ tr.sort-controls th {
25
+ background-color: #999 !important;
26
+ font-size: .75em;
27
+ text-transform: uppercase;
28
+ text-align: left;
29
+ }
63
30
 
31
+ tr.header th, tfoot { background-color: #444 !important; color: #fff; }
32
+ tfoot td { font-size: .75em; }
33
+ tr.odd td { background-color: #eee; }
64
34
  div.dataTables_filter { display: none !important; }
65
35
  div.dataTables_paginate { display: none !important; }
66
36
  div.dataTables_info { display: none !important; }
67
37
 
68
-
69
38
  %body
70
39
  .container
71
40
  %table{class: "output-table"}
@@ -79,8 +48,8 @@
79
48
  = path_to_file
80
49
  .file_meta_right
81
50
  %h3
82
- = "Overall Complexity: #{complexity}"
83
- %tr
51
+ = "Average Complexity: #{average_complexity}"
52
+ %tr.sort-controls
84
53
  = header
85
54
  %tbody
86
55
  = rows
@@ -97,7 +66,7 @@
97
66
  Fukuzatsu
98
67
  %br
99
68
  %input{onclick: "history.back(-1)", type: "button", value: "Back"}
100
-
69
+ %br
101
70
  %br
102
71
 
103
72
  .file_listing
@@ -1,56 +1,68 @@
1
- module Formatters
1
+ module Fukuzatsu
2
2
 
3
- require 'terminal-table'
4
- require 'rainbow/ext/string'
3
+ module Formatters
5
4
 
6
- class Text
5
+ require 'terminal-table'
6
+ require 'rainbow/ext/string'
7
7
 
8
- include Formatters::Base
8
+ class Text
9
9
 
10
- def self.has_index?
11
- false
12
- end
10
+ include Formatters::Base
13
11
 
14
- def self.writes_to_file_system?
15
- false
16
- end
12
+ def self.explain(count); end
17
13
 
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
14
+ def self.reset_output_directory
15
+ end
24
16
 
25
- def header
26
- ["Class/Module", "Method", "Complexity"]
27
- end
17
+ def self.writes_to_file_system?
18
+ false
19
+ end
28
20
 
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
21
+ def color_for(entity, average_complexity)
22
+ return :green if entity.complexity == 0
23
+ return :yellow if entity.complexity <= average_complexity
24
+ return :red if entity.complexity > average_complexity
25
+ return :white
26
+ end
40
27
 
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)]
28
+ def header
29
+ ["Class/Module", "Method", "Complexity"]
30
+ end
31
+
32
+ def export
33
+ table = Terminal::Table.new(
34
+ title: "#{summary.source_file}".color(:white),
35
+ headings: header,
36
+ rows: rows,
37
+ )
38
+ table.align_column(3, :right)
39
+ puts table
40
+ end
41
+
42
+ def rows
43
+ rows_for(
44
+ [self.summary, self.summary.summaries].flatten,
45
+ self.summary.average_complexity
46
+ )
47
+ end
48
+
49
+ def rows_for(summaries, average_complexity)
50
+ summaries.map do |summary|
51
+ color = color_for(summary, average_complexity)
52
+ [
53
+ wrap("#{summary.container_name}").color(color),
54
+ wrap("#{summary.entity_name}".color(color)),
55
+ "#{summary.complexity}".color(color)
56
+ ]
57
+ end.compact
58
+ end
59
+
60
+ def wrap(string)
61
+ return string if string.length < 50
62
+ string[0..49] << "..."
45
63
  end
46
- end
47
64
 
48
- def wrap(string)
49
- return string if string.length < 25
50
- string[0..20] << "..."
51
65
  end
52
66
 
53
67
  end
54
-
55
68
  end
56
-
@@ -1,76 +1,58 @@
1
1
  require 'fileutils'
2
+ require 'analyst'
2
3
 
3
4
  module Fukuzatsu
4
5
 
5
6
  class Parser
6
7
 
7
- attr_reader :start_path, :parsed_files
8
- attr_reader :threshold, :formatter
9
- attr_reader :start_time
8
+ attr_reader :path_to_files, :formatter, :threshold
10
9
 
11
- OUTPUT_DIRECTORY = "doc/fukuzatsu"
12
-
13
- def initialize(path, formatter, threshold=0)
14
- @start_path = path
15
- @formatter = formatter
16
- @threshold = threshold
17
- @start_time = Time.now
18
- reset_output_directory
19
- end
20
-
21
- def parse_files
22
- @parsed_files = source_files.map do |path_to_file|
23
- parse_source_file(path_to_file)
24
- end
10
+ def initialize(path_to_files, formatter, threshold)
11
+ @path_to_files = path_to_files
12
+ @formatter = formatter
13
+ @threshold = threshold
25
14
  end
26
15
 
27
16
  def report
28
- self.parsed_files.each do |file|
29
- print "."
30
- formatter.new(file, OUTPUT_DIRECTORY, file.source).export
17
+ self.formatter.reset_output_directory
18
+ self.formatter.index(summaries)
19
+ summaries.uniq(&:container_name).each do |summary|
20
+ self.formatter.new(summary: summary).export
31
21
  end
32
- puts
33
- write_report_index
34
- report_complexity
22
+ self.formatter.explain(summaries.count)
23
+ check_complexity
35
24
  end
36
25
 
37
26
  private
38
27
 
39
- def reset_output_directory
40
- begin
41
- FileUtils.remove_dir(OUTPUT_DIRECTORY)
42
- rescue Errno::ENOENT
43
- end
44
- FileUtils.mkpath(OUTPUT_DIRECTORY)
28
+ def check_complexity
29
+ return if self.threshold == 0
30
+ return unless complexity_exceeds_threshold?
31
+ puts "Maximum average complexity of #{average_complexities.max} exceeds #{self.threshold.to_f} threshold!"
32
+ exit 1
45
33
  end
46
34
 
47
- def source_files
48
- if File.directory?(start_path)
49
- return Dir.glob(File.join(start_path, "**", "*.rb"))
50
- else
51
- return [start_path]
52
- end
35
+ def average_complexities
36
+ average_complexities ||= summaries.map(&:average_complexity)
53
37
  end
54
38
 
55
- def parse_source_file(path_to_file, options={})
56
- ParsedFile.new(path_to_file: path_to_file)
39
+ def complexity_exceeds_threshold?
40
+ average_complexities.max.to_f > self.threshold.to_f
57
41
  end
58
42
 
59
- def report_complexity
60
- return if self.threshold == 0
61
- complexities = self.parsed_files.map(&:complexity)
62
- return if complexities.max.to_i <= self.threshold
63
- puts "Maximum complexity of #{complexities.max} exceeds #{options['threshold']} threshold!"
64
- exit 1
43
+ def summaries
44
+ @summaries ||= file_reader.source_files.map do |source_file|
45
+ Fukuzatsu::Summary.from(
46
+ content: source_file.contents,
47
+ source_file: source_file.filename
48
+ )
49
+ end.flatten
65
50
  end
66
51
 
67
- def write_report_index
68
- return unless self.formatter.writes_to_file_system?
69
- puts "Results written to #{OUTPUT_DIRECTORY} "
70
- return unless self.formatter.has_index?
71
- formatter.index_class.new(parsed_files.map(&:summary), OUTPUT_DIRECTORY).export
52
+ def file_reader
53
+ @file_reader ||= Fukuzatsu::FileReader.new(self.path_to_files)
72
54
  end
73
55
 
74
56
  end
75
57
 
76
- end
58
+ end