fukuzatsu 0.10.1 → 1.0.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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 98e183078ac1f80b93a9f0c432ff70a5a75fb156
4
- data.tar.gz: 9ff83590fd2e21f4dab4100db0bb32dc51d09468
3
+ metadata.gz: d1fc37f05fcd183b24dbe2ed09a7d48b5214d5cb
4
+ data.tar.gz: fd03679df8338b84b667fb9c084fdc5466c442b3
5
5
  SHA512:
6
- metadata.gz: 361c28387778b0fa9211c4401dc9d6ebfd1ad491a0a1c6857a0c57ccc32b052d3b4ec81fefbd30ca02a0304800120b89ff6781ae3188c5de8e553cb834fbcc8b
7
- data.tar.gz: e8d4b595508e6fd5db7d3f057dc2325d3d1c7c694729f6caf8b09fcf6c29f8cbc79d5ab5513df4a57f6daf5944a42d15295af2c8cfb00ec8e8db4c6fb6eeec39
6
+ metadata.gz: 0e96102937e251544859c12e3bd403d07fb405d1509c4f46369122aeebb2eeea0037aece27e8c8f78d11ef07b9452ece1e6a2925d0962eaa5a8d5281c3021978
7
+ data.tar.gz: 453b198fd14b8eabd98ac67a9375fdcfd2f41ae64254ed0ab488718e80beb11d54660a34a8c12d280a4d0509a848933406b5c1ae0453fcff2a667ce4cccb45f7
@@ -1,5 +1,6 @@
1
- ODE
2
- ## Version 0.4
1
+ # CODE OF CONDUCT
2
+
3
+ This project has adopted version 0.4 of the [Contributor Covenant](https://github.com/bantik/contributor_covenant/).
3
4
 
4
5
  As contributors and maintainers of this project, we pledge to respect all people who contribute through reporting issues, posting feature requests, updating documentation, submitting pull requests or patches, and other activities.
5
6
 
data/README.md CHANGED
@@ -1,16 +1,13 @@
1
1
  # Fukuzatsu
2
2
 
3
- *Note: this gem is a work in progress and should not be considered production-ready until version 1.0*
4
-
5
3
  Fukuzatsu ("complexity") is a tool for measuring code complexity in Ruby class files. Its analysis
6
- generates relative complexity figures similar to the results of cyclomatic complexity algorithms.
4
+ generates relative complexity figures similar to the results of cyclomatic complexity algorithms.
7
5
  (You can learn more about cyclomatic complexity at http://en.wikipedia.org/wiki/Cyclomatic_complexity)
8
6
 
9
- Why should you care about this kind of complexity? More complex code tends to attract bugs and to
7
+ Why should you care about this kind of complexity? More complex code tends to attract bugs and to
10
8
  increase the friction around extending features or refactoring code.
11
9
 
12
- It was inspired by Saikuro, written by Zev Blut.
13
- Fukuzatsu was created by [Coraline Ada Ehmke](http://where.coraline.codes/) with invaluable assistance from [Mike Ziwisky](https://github.com/mziwisky).
10
+ Fukuzatsu was created by [Coraline Ada Ehmke](http://where.coraline.codes/) with invaluable assistance from [Mike Ziwisky](https://github.com/mziwisky). It was inspired by Saikuro, written by Zev Blut.
14
11
 
15
12
  ## Screenshots
16
13
 
@@ -22,7 +22,9 @@ Gem::Specification.new do |spec|
22
22
  spec.add_dependency "poro_plus"
23
23
  spec.add_dependency "haml"
24
24
  spec.add_dependency "parser"
25
+ spec.add_dependency "rainbow"
25
26
  spec.add_dependency "rouge"
27
+ spec.add_dependency "terminal-table"
26
28
  spec.add_dependency "thor"
27
29
 
28
30
  spec.add_development_dependency "bundler", "~> 1.6"
@@ -13,6 +13,7 @@ require_relative "fukuzatsu/formatters/text"
13
13
  require_relative "fukuzatsu/line_of_code"
14
14
  require_relative "fukuzatsu/parsed_file"
15
15
  require_relative "fukuzatsu/parsed_method"
16
+ require_relative "fukuzatsu/parser"
16
17
  require_relative "fukuzatsu/version"
17
18
 
18
19
  module Fukuzatsu
@@ -53,10 +53,18 @@ class Analyzer
53
53
  if node.type == :def || node.type == :defs || node.type == :class
54
54
  name = node.loc.name
55
55
  expression = node.loc.expression
56
+ type = case(node.type)
57
+ when :defs
58
+ :class
59
+ when :def
60
+ :instance
61
+ when :class
62
+ :none
63
+ end
56
64
  methods << ParsedMethod.new(
57
65
  name: content[name.begin_pos..name.end_pos - 1],
58
66
  content: content[expression.begin_pos..expression.end_pos - 1],
59
- type: [:defs, :class].include?(node.type) ? :class : :instance
67
+ type: type
60
68
  )
61
69
  end
62
70
  node.children.each do |child|
@@ -13,29 +13,19 @@ module Fukuzatsu
13
13
  method_option :threshold, :type => :numeric, :default => 0, :aliases => "-t"
14
14
 
15
15
  def check(path="./")
16
- file_list(path).each{ |path_to_file| parse(path_to_file) }
17
- handle_index(summaries)
18
- report
16
+ parser = Fukuzatsu::Parser.new(
17
+ path,
18
+ formatter,
19
+ options['threshold']
20
+ )
21
+ parser.parse_files
22
+ parser.report
19
23
  end
20
24
 
21
25
  default_task :check
22
26
 
23
- attr_accessor :summaries, :last_file
24
-
25
27
  private
26
28
 
27
- def complexities
28
- summaries.to_a.map{|s| s[:complexity]}
29
- end
30
-
31
- def file_list(start_file)
32
- if File.directory?(start_file)
33
- return Dir.glob(File.join(start_file, "**", "*.rb"))
34
- else
35
- return [start_file]
36
- end
37
- end
38
-
39
29
  def formatter
40
30
  case options['format']
41
31
  when 'html'
@@ -47,41 +37,6 @@ module Fukuzatsu
47
37
  end
48
38
  end
49
39
 
50
- def handle_index(file_summary)
51
- return unless options['format'] == 'html'
52
- index = Formatters::HtmlIndex.new(file_summary)
53
- self.last_file = File.join(index.output_path, index.filename)
54
- index.export
55
- end
56
-
57
- def parse(path_to_file, options={})
58
- file = ParsedFile.new(path_to_file: path_to_file)
59
- parser = formatter.new(file, file.source)
60
- parser.export
61
- self.summaries ||= []
62
- self.summaries << file.summary.merge(results_file: parser.path_to_results)
63
- last_file = file
64
- end
65
-
66
- def report
67
- unless options['format'] == "text"
68
- puts "Results written to:"
69
- puts last_file.present? && "#{last_file}" || results_files.join("\r\n")
70
- end
71
- report_complexity
72
- end
73
-
74
- def report_complexity
75
- return if options['threshold'].to_i == 0
76
- return if complexities.max.to_i <= options['threshold']
77
- puts "Maximum complexity of #{complexities.max} exceeds #{options['threshold']} threshold!"
78
- exit 1
79
- end
80
-
81
- def results_files
82
- summaries.map{|s| s[:results_file]}
83
- end
84
-
85
40
  end
86
41
 
87
42
  end
@@ -5,27 +5,21 @@ module Formatters
5
5
  def self.included(klass)
6
6
  klass.send(:attr_accessor, :file)
7
7
  klass.send(:attr_accessor, :source)
8
+ klass.send(:attr_accessor, :output_directory)
8
9
  end
9
10
 
10
- def initialize(file, source="")
11
+ def initialize(file, output_directory, source="")
11
12
  self.file = file
12
13
  self.source = source
14
+ self.output_directory = output_directory
13
15
  end
14
16
 
15
- def content
16
- [header, rows, footer].flatten.join("\r\n")
17
- end
18
-
19
- def columns
20
- ["class", "method", "complexity"]
21
- end
22
-
23
- def root_path
24
- "doc/fukuzatsu"
17
+ def filename
18
+ File.basename(self.file.path_to_file) + file_extension
25
19
  end
26
20
 
27
21
  def output_path
28
- output_path = File.dirname(File.join(root_path, self.file.path_to_file))
22
+ output_path = File.dirname(File.join(self.output_directory, self.file.path_to_file))
29
23
  FileUtils.mkpath(output_path)
30
24
  output_path
31
25
  end
@@ -34,22 +28,6 @@ module Formatters
34
28
  File.join(output_path, filename)
35
29
  end
36
30
 
37
- def filename
38
- File.basename(self.file.path_to_file) + file_extension
39
- end
40
-
41
- def file_extension
42
- ""
43
- end
44
-
45
- def export
46
- begin
47
- File.open(path_to_results, 'w') {|outfile| outfile.write(content)}
48
- rescue Exception => e
49
- puts "Unable to write output: #{e} #{e.backtrace}"
50
- end
51
- end
52
-
53
31
  end
54
32
 
55
33
  end
@@ -4,24 +4,40 @@ module Formatters
4
4
 
5
5
  include Formatters::Base
6
6
 
7
- def header
8
- columns.join(',')
7
+ def self.has_index?
8
+ false
9
9
  end
10
10
 
11
- def rows
12
- file.methods.inject([]) do |a, method|
13
- a << "#{file.class_name},#{method.prefix}#{method.name},#{method.complexity}"
14
- a
15
- end.join("\r\n")
11
+ def self.writes_to_file_system?
12
+ true
16
13
  end
17
14
 
18
- def footer
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
19
25
  end
20
26
 
21
27
  def file_extension
22
28
  ".csv"
23
29
  end
24
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.prefix}#{method.name},#{method.complexity}"
38
+ end.join("\r\n")
39
+ end
40
+
25
41
  end
26
42
 
27
43
  end
@@ -6,8 +6,20 @@ module Formatters
6
6
 
7
7
  include Formatters::Base
8
8
 
9
- def header
10
- columns.map{|col| "<th>#{col.titleize}</th>"}.join("\r\n")
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"]
11
23
  end
12
24
 
13
25
  def content
@@ -25,10 +37,22 @@ module Formatters
25
37
  )
26
38
  end
27
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
+
28
48
  def formatter
29
49
  Rouge::Formatters::HTML.new(line_numbers: true)
30
50
  end
31
51
 
52
+ def header
53
+ columns.map{|col| "<th>#{col.titleize}</th>"}.join("\r\n")
54
+ end
55
+
32
56
  def lexer
33
57
  lexer = Rouge::Lexers::Ruby.new
34
58
  end
@@ -54,9 +78,6 @@ module Formatters
54
78
  end.join("\r\n")
55
79
  end
56
80
 
57
- def footer
58
- end
59
-
60
81
  def file_extension
61
82
  ".htm"
62
83
  end
@@ -4,19 +4,11 @@ module Formatters
4
4
 
5
5
  include Formatters::Base
6
6
 
7
- attr_accessor :file_summary
7
+ attr_reader :file_summary, :output_directory
8
8
 
9
- def initialize(file_summary)
10
- self.file_summary = file_summary
11
- end
12
-
13
- def filename
14
- "index.htm"
15
- end
16
-
17
- def output_path
18
- FileUtils.mkpath(root_path)
19
- root_path
9
+ def initialize(file_summary, output_directory)
10
+ @file_summary = file_summary
11
+ @output_directory = output_directory
20
12
  end
21
13
 
22
14
  def content
@@ -29,6 +21,23 @@ module Formatters
29
21
  )
30
22
  end
31
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
+
32
41
  def output_template
33
42
  File.read(File.dirname(__FILE__) + "/templates/index.html.haml")
34
43
  end
@@ -1,26 +1,50 @@
1
1
  module Formatters
2
2
 
3
+ require 'terminal-table'
4
+ require 'rainbow/ext/string'
5
+
3
6
  class Text
4
7
 
5
8
  include Formatters::Base
6
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
+
7
25
  def header
8
- "#{file.class_name}\t\t#{file.complexity}"
26
+ ["Class/Module", "Method", "Complexity"]
9
27
  end
10
28
 
11
29
  def export
12
- puts content
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: 80}
36
+ )
37
+ table.align_column(3, :right)
38
+ puts table
13
39
  end
14
40
 
15
41
  def rows
16
42
  file.methods.map do |method|
17
- "#{file.class_name}\t#{method.prefix}#{method.name}\t#{method.complexity}"
43
+ color = color_for(method)
44
+ ["#{file.class_name}".color(color), "#{method.name}".color(color), "#{method.complexity}".color(color)]
18
45
  end
19
46
  end
20
47
 
21
- def footer
22
- end
23
-
24
48
  end
25
49
 
26
50
  end
@@ -18,6 +18,10 @@ class ParsedFile
18
18
  @analyzer ||= Analyzer.new(content)
19
19
  end
20
20
 
21
+ def average_complexity
22
+ methods.map(&:complexity).reduce(:+) / methods.count.to_f
23
+ end
24
+
21
25
  def complexity
22
26
  @complexity ||= analyzer.complexity
23
27
  end
@@ -40,7 +44,6 @@ class ParsedFile
40
44
 
41
45
  def summary
42
46
  {
43
- results_file: self.path_to_results,
44
47
  path_to_file: self.path_to_file,
45
48
  source: source,
46
49
  class_name: self.class_name,
@@ -8,8 +8,15 @@ class ParsedMethod
8
8
  @complexity ||= analyzer.complexity
9
9
  end
10
10
 
11
+ def name
12
+ return "" if self.type == :none
13
+ "#{prefix}#{@name}"
14
+ end
15
+
11
16
  def prefix
12
- self.type.to_s == 'class' ? "." : "#"
17
+ return "." if self.type == :class
18
+ return "#" if self.type == :instance
19
+ return "*"
13
20
  end
14
21
 
15
22
  def analyzer
@@ -0,0 +1,68 @@
1
+ require 'fileutils'
2
+
3
+ module Fukuzatsu
4
+
5
+ class Parser
6
+
7
+ attr_reader :start_path, :parsed_files
8
+ attr_reader :threshold, :formatter
9
+
10
+ OUTPUT_DIRECTORY = "doc/fukuzatsu"
11
+
12
+ def initialize(path, formatter, threshold=0)
13
+ @start_path = path
14
+ @formatter = formatter
15
+ @threshold = threshold
16
+ reset_output_directory
17
+ end
18
+
19
+ def parse_files
20
+ @parsed_files = source_files.map{ |path_to_file| parse_source_file(path_to_file) }
21
+ end
22
+
23
+ def report
24
+ self.parsed_files.each{ |file| formatter.new(file, OUTPUT_DIRECTORY, file.source).export }
25
+ write_report_index
26
+ report_complexity
27
+ end
28
+
29
+ private
30
+
31
+ def reset_output_directory
32
+ begin
33
+ FileUtils.remove_dir(OUTPUT_DIRECTORY)
34
+ rescue Errno::ENOENT
35
+ end
36
+ FileUtils.mkpath(OUTPUT_DIRECTORY)
37
+ end
38
+
39
+ def source_files
40
+ if File.directory?(start_path)
41
+ return Dir.glob(File.join(start_path, "**", "*.rb"))
42
+ else
43
+ return [start_path]
44
+ end
45
+ end
46
+
47
+ def parse_source_file(path_to_file, options={})
48
+ ParsedFile.new(path_to_file: path_to_file)
49
+ end
50
+
51
+ def report_complexity
52
+ return if self.threshold == 0
53
+ complexities = self.parsed_files.map(&:complexity)
54
+ return if complexities.max.to_i <= self.threshold
55
+ puts "Maximum complexity of #{complexities.max} exceeds #{options['threshold']} threshold!"
56
+ exit 1
57
+ end
58
+
59
+ def write_report_index
60
+ return unless self.formatter.writes_to_file_system?
61
+ puts "Results written to #{OUTPUT_DIRECTORY} "
62
+ return unless self.formatter.has_index?
63
+ formatter.index_class.new(parsed_files.map(&:summary), OUTPUT_DIRECTORY).export
64
+ end
65
+
66
+ end
67
+
68
+ end
@@ -1,3 +1,3 @@
1
1
  module Fukuzatsu
2
- VERSION = "0.10.1"
2
+ VERSION = "1.0.0"
3
3
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: fukuzatsu
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.10.1
4
+ version: 1.0.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Bantik
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2014-09-22 00:00:00.000000000 Z
11
+ date: 2014-10-07 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: ephemeral
@@ -66,6 +66,20 @@ dependencies:
66
66
  - - ">="
67
67
  - !ruby/object:Gem::Version
68
68
  version: '0'
69
+ - !ruby/object:Gem::Dependency
70
+ name: rainbow
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - ">="
74
+ - !ruby/object:Gem::Version
75
+ version: '0'
76
+ type: :runtime
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - ">="
81
+ - !ruby/object:Gem::Version
82
+ version: '0'
69
83
  - !ruby/object:Gem::Dependency
70
84
  name: rouge
71
85
  requirement: !ruby/object:Gem::Requirement
@@ -80,6 +94,20 @@ dependencies:
80
94
  - - ">="
81
95
  - !ruby/object:Gem::Version
82
96
  version: '0'
97
+ - !ruby/object:Gem::Dependency
98
+ name: terminal-table
99
+ requirement: !ruby/object:Gem::Requirement
100
+ requirements:
101
+ - - ">="
102
+ - !ruby/object:Gem::Version
103
+ version: '0'
104
+ type: :runtime
105
+ prerelease: false
106
+ version_requirements: !ruby/object:Gem::Requirement
107
+ requirements:
108
+ - - ">="
109
+ - !ruby/object:Gem::Version
110
+ version: '0'
83
111
  - !ruby/object:Gem::Dependency
84
112
  name: thor
85
113
  requirement: !ruby/object:Gem::Requirement
@@ -183,6 +211,7 @@ files:
183
211
  - lib/fukuzatsu/line_of_code.rb
184
212
  - lib/fukuzatsu/parsed_file.rb
185
213
  - lib/fukuzatsu/parsed_method.rb
214
+ - lib/fukuzatsu/parser.rb
186
215
  - lib/fukuzatsu/version.rb
187
216
  - spec/analyzer_spec.rb
188
217
  - spec/cli_spec.rb