fukuzatsu 0.9.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 +7 -0
- data/.gitignore +23 -0
- data/.rspec +2 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +22 -0
- data/README.md +29 -0
- data/Rakefile +2 -0
- data/bin/fuku +3 -0
- data/fukuzatsu.gemspec +32 -0
- data/lib/fukuzatsu.rb +10 -0
- data/lib/fukuzatsu/analyzer.rb +82 -0
- data/lib/fukuzatsu/cli.rb +24 -0
- data/lib/fukuzatsu/formatters/base.rb +44 -0
- data/lib/fukuzatsu/formatters/csv.rb +27 -0
- data/lib/fukuzatsu/formatters/html.rb +50 -0
- data/lib/fukuzatsu/formatters/templates/output.html.haml +44 -0
- data/lib/fukuzatsu/formatters/text.rb +27 -0
- data/lib/fukuzatsu/parsed_file.rb +28 -0
- data/lib/fukuzatsu/parsed_method.rb +17 -0
- data/lib/fukuzatsu/version.rb +3 -0
- data/spec/analyzer_spec.rb +28 -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/formatters/csv_spec.rb +42 -0
- data/spec/spec_helper.rb +3 -0
- metadata +202 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 9049fb6f92f869e6d799c87e622079d696eb8f7b
|
4
|
+
data.tar.gz: 7e75d3585e1b60d3d557dec69d74afa14393b8f4
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: f3f8382ccb36cc0acbf279213e59d38b3838279d66427a78be19ececa27ccb8b328e966c0990f43fd28bed8adeec8e418343661eb723c69a85e7c3fe85fc797b
|
7
|
+
data.tar.gz: 5c98337d7d5654c973fe0281802fd53f96569625bc8015bbb1ac904d0c3e3eb2c242023f466625b6181943a9cef7fedd8d3ec5a552053493d51401f540e886fc
|
data/.gitignore
ADDED
@@ -0,0 +1,23 @@
|
|
1
|
+
*.gem
|
2
|
+
*.rbc
|
3
|
+
.bundle
|
4
|
+
.config
|
5
|
+
.yardoc
|
6
|
+
Gemfile.lock
|
7
|
+
InstalledFiles
|
8
|
+
_yardoc
|
9
|
+
coverage
|
10
|
+
doc/
|
11
|
+
lib/bundler/man
|
12
|
+
pkg
|
13
|
+
rdoc
|
14
|
+
spec/reports
|
15
|
+
test/tmp
|
16
|
+
test/version_tmp
|
17
|
+
tmp
|
18
|
+
*.bundle
|
19
|
+
*.so
|
20
|
+
*.o
|
21
|
+
*.a
|
22
|
+
mkmf.log
|
23
|
+
.console_history
|
data/.rspec
ADDED
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2014 Bantik
|
2
|
+
|
3
|
+
MIT License
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
6
|
+
a copy of this software and associated documentation files (the
|
7
|
+
"Software"), to deal in the Software without restriction, including
|
8
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
9
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
10
|
+
permit persons to whom the Software is furnished to do so, subject to
|
11
|
+
the following conditions:
|
12
|
+
|
13
|
+
The above copyright notice and this permission notice shall be
|
14
|
+
included in all copies or substantial portions of the Software.
|
15
|
+
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
17
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
18
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
19
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
20
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
21
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
22
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,29 @@
|
|
1
|
+
# Fukuzatsu
|
2
|
+
|
3
|
+
Fukuzatsu ("complexity") is a tool for measuring code complexity. Its analysis is based on a cycomatic complexity algorithm.
|
4
|
+
|
5
|
+
You can learn more about cyclomatic complexity at http://en.wikipedia.org/wiki/Cyclomatic_complexity
|
6
|
+
|
7
|
+
Why should you care about this kind of complexity? More complex code tends to attract bugs and to increase the friction around extending features or refactoring code.
|
8
|
+
|
9
|
+
Fukuzatsu was inspired by Saikuro, written by Zev Blut.
|
10
|
+
|
11
|
+
## Installation
|
12
|
+
|
13
|
+
Install the gem:
|
14
|
+
|
15
|
+
$ gem install fukuzatsu
|
16
|
+
|
17
|
+
This installs the CLI tool.
|
18
|
+
|
19
|
+
## Usage
|
20
|
+
|
21
|
+
fuku parse path/to/file/my_file.rb
|
22
|
+
|
23
|
+
## Contributing
|
24
|
+
|
25
|
+
1. Fork it
|
26
|
+
2. Create your feature branch (`git checkout -b my-new-feature`)
|
27
|
+
3. Commit your changes (`git commit -am 'Add some feature'`)
|
28
|
+
4. Push to the branch (`git push origin my-new-feature`)
|
29
|
+
5. Create a new Pull Request
|
data/Rakefile
ADDED
data/bin/fuku
ADDED
data/fukuzatsu.gemspec
ADDED
@@ -0,0 +1,32 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'fukuzatsu/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = "fukuzatsu"
|
8
|
+
spec.version = Fukuzatsu::VERSION
|
9
|
+
spec.authors = ["Bantik"]
|
10
|
+
spec.email = ["coraline@idolhands.com"]
|
11
|
+
spec.summary = "A simple code complexity analyzer."
|
12
|
+
spec.description = "Calculates the cyclomatic complexity of methods within a given file."
|
13
|
+
spec.homepage = "https://gitlab.com/coraline/fukuzatsu"
|
14
|
+
spec.license = "MIT"
|
15
|
+
|
16
|
+
spec.files = `git ls-files -z`.split("\x0")
|
17
|
+
spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
|
18
|
+
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
|
19
|
+
spec.require_paths = ["lib"]
|
20
|
+
|
21
|
+
spec.add_dependency "require_all"
|
22
|
+
spec.add_dependency "ephemeral"
|
23
|
+
spec.add_dependency "poro_plus"
|
24
|
+
spec.add_dependency "haml"
|
25
|
+
spec.add_dependency "parser"
|
26
|
+
|
27
|
+
spec.add_development_dependency "bundler", "~> 1.6"
|
28
|
+
spec.add_development_dependency "rake"
|
29
|
+
spec.add_development_dependency "rspec"
|
30
|
+
spec.add_development_dependency "thor"
|
31
|
+
|
32
|
+
end
|
data/lib/fukuzatsu.rb
ADDED
@@ -0,0 +1,82 @@
|
|
1
|
+
require 'parser/current'
|
2
|
+
|
3
|
+
class Analyzer
|
4
|
+
|
5
|
+
CONDITIONALS = [:if]
|
6
|
+
|
7
|
+
attr_accessor :content, :class_name, :edges, :nodes, :exits
|
8
|
+
|
9
|
+
def initialize(content)
|
10
|
+
self.content = content
|
11
|
+
self.edges = 0
|
12
|
+
self.nodes = 1
|
13
|
+
self.exits = 1
|
14
|
+
end
|
15
|
+
|
16
|
+
def complexity
|
17
|
+
return unless traverse(parsed)
|
18
|
+
self.edges - self.nodes + exits
|
19
|
+
end
|
20
|
+
|
21
|
+
def extract_methods
|
22
|
+
@methods ||= methods_from(parsed)
|
23
|
+
end
|
24
|
+
|
25
|
+
def extract_class_name
|
26
|
+
return self.class_name if self.class_name
|
27
|
+
name = parsed.children.select{|node| node.type == :class}.first.loc.name
|
28
|
+
self.class_name = self.content[name.begin_pos..(name.end_pos - 1)]
|
29
|
+
end
|
30
|
+
|
31
|
+
private
|
32
|
+
|
33
|
+
def extend_graph
|
34
|
+
self.edges += 2
|
35
|
+
self.nodes += 2
|
36
|
+
self.exits += 1
|
37
|
+
end
|
38
|
+
|
39
|
+
def methods_from(node, methods=[])
|
40
|
+
if node.type == :def || node.type == :defs
|
41
|
+
name = node.loc.name
|
42
|
+
expression = node.loc.expression
|
43
|
+
methods << ParsedMethod.new(
|
44
|
+
name: content[name.begin_pos..name.end_pos - 1],
|
45
|
+
content: content[expression.begin_pos..expression.end_pos - 1],
|
46
|
+
type: node.type == :defs ? :class : :instance
|
47
|
+
)
|
48
|
+
end
|
49
|
+
node.children.each do |child|
|
50
|
+
if parent_node?(child)
|
51
|
+
methods_from(child, methods)
|
52
|
+
end
|
53
|
+
end
|
54
|
+
methods
|
55
|
+
end
|
56
|
+
|
57
|
+
def parent_node?(node)
|
58
|
+
node.respond_to?(:type) || node.respond_to?(:children)
|
59
|
+
end
|
60
|
+
|
61
|
+
def parse!
|
62
|
+
traverse(parsed) && complexity
|
63
|
+
end
|
64
|
+
|
65
|
+
def parsed
|
66
|
+
@parsed ||= Parser::CurrentRuby.parse(content)
|
67
|
+
end
|
68
|
+
|
69
|
+
def traverse(node, accumulator=[], extract_methods=false)
|
70
|
+
accumulator << node.type
|
71
|
+
extend_graph if CONDITIONALS.include?(node.type)
|
72
|
+
node.children.each do |child|
|
73
|
+
if parent_node?(child)
|
74
|
+
accumulator << child.type
|
75
|
+
traverse(child, accumulator)
|
76
|
+
end
|
77
|
+
end
|
78
|
+
accumulator
|
79
|
+
end
|
80
|
+
|
81
|
+
end
|
82
|
+
|
@@ -0,0 +1,24 @@
|
|
1
|
+
require 'thor'
|
2
|
+
require 'fukuzatsu'
|
3
|
+
|
4
|
+
module Fukuzatsu
|
5
|
+
|
6
|
+
class CLI < Thor
|
7
|
+
|
8
|
+
desc "parse PATH_TO_FILE -f FORMAT", "Formats are text (default, to STDOUT), html, and csv. Ex: parse foo.rb -f html"
|
9
|
+
method_option :format, :type => :string, :default => 'text', :aliases => "-f"
|
10
|
+
def parse(file)
|
11
|
+
file = ParsedFile.new(path_to_file: file)
|
12
|
+
case options['format']
|
13
|
+
when 'html'
|
14
|
+
Formatters::Html.new(file).export
|
15
|
+
when 'csv'
|
16
|
+
Formatters::Csv.new(file).export
|
17
|
+
else
|
18
|
+
Formatters::Text.new(file).export
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
end
|
23
|
+
|
24
|
+
end
|
@@ -0,0 +1,44 @@
|
|
1
|
+
module Formatters
|
2
|
+
|
3
|
+
module Base
|
4
|
+
|
5
|
+
def self.included(klass)
|
6
|
+
klass.send(:attr_accessor, :file)
|
7
|
+
end
|
8
|
+
|
9
|
+
def initialize(file)
|
10
|
+
self.file = file
|
11
|
+
end
|
12
|
+
|
13
|
+
def content
|
14
|
+
[header, rows, footer].flatten.join("\r\n")
|
15
|
+
end
|
16
|
+
|
17
|
+
def columns
|
18
|
+
["class", "method", "complexity"]
|
19
|
+
end
|
20
|
+
|
21
|
+
def output_path
|
22
|
+
output_path = "doc/fukuzatsu/#{self.file.path_to_file.split('/')[0..-2].join("/")}"
|
23
|
+
FileUtils.mkpath(output_path)
|
24
|
+
output_path
|
25
|
+
end
|
26
|
+
|
27
|
+
def filename
|
28
|
+
self.file.path_to_file.split('/')[-1] + file_extension
|
29
|
+
end
|
30
|
+
|
31
|
+
def export
|
32
|
+
begin
|
33
|
+
outfile = File.open("#{output_path}/#{filename}", 'w')
|
34
|
+
outfile.write(content)
|
35
|
+
rescue Exception => e
|
36
|
+
puts "Unable to write output: #{e} #{e.backtrace}"
|
37
|
+
ensure
|
38
|
+
outfile && outfile.close
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
end
|
43
|
+
|
44
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
module Formatters
|
2
|
+
|
3
|
+
class Csv
|
4
|
+
|
5
|
+
include Formatters::Base
|
6
|
+
|
7
|
+
def header
|
8
|
+
columns.join(',')
|
9
|
+
end
|
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")
|
16
|
+
end
|
17
|
+
|
18
|
+
def footer
|
19
|
+
end
|
20
|
+
|
21
|
+
def file_extension
|
22
|
+
".csv"
|
23
|
+
end
|
24
|
+
|
25
|
+
end
|
26
|
+
|
27
|
+
end
|
@@ -0,0 +1,50 @@
|
|
1
|
+
module Formatters
|
2
|
+
|
3
|
+
class Html
|
4
|
+
|
5
|
+
include Formatters::Base
|
6
|
+
|
7
|
+
def header
|
8
|
+
columns.map{|col| "<th>#{col.titleize}</th>"}.join("\r\n")
|
9
|
+
end
|
10
|
+
|
11
|
+
def content
|
12
|
+
Haml::Engine.new(template).render(Object.new, {
|
13
|
+
header: header,
|
14
|
+
rows: rows,
|
15
|
+
class_name: file.class_name,
|
16
|
+
path_to_file: file.path_to_file,
|
17
|
+
date: Time.now.strftime("%Y/%m/%d"),
|
18
|
+
time: Time.now.strftime("%l:%M %P")
|
19
|
+
}
|
20
|
+
)
|
21
|
+
end
|
22
|
+
|
23
|
+
def template
|
24
|
+
File.read(File.dirname(__FILE__) + "/templates/output.html.haml")
|
25
|
+
end
|
26
|
+
|
27
|
+
def rows
|
28
|
+
i = 0
|
29
|
+
file.methods.inject([]) do |a, method|
|
30
|
+
i += 1
|
31
|
+
a << "<tr class='#{i % 2 == 1 ? 'even' : 'odd'}'>"
|
32
|
+
a << " <td>#{file.class_name}</td>"
|
33
|
+
a << " <td>#{method.prefix}#{method.name}</td>"
|
34
|
+
a << " <td>#{method.complexity}</td>"
|
35
|
+
a << "</tr>"
|
36
|
+
a
|
37
|
+
end.join("\r\n")
|
38
|
+
end
|
39
|
+
|
40
|
+
def footer
|
41
|
+
end
|
42
|
+
|
43
|
+
def file_extension
|
44
|
+
".htm"
|
45
|
+
end
|
46
|
+
|
47
|
+
|
48
|
+
end
|
49
|
+
|
50
|
+
end
|
@@ -0,0 +1,44 @@
|
|
1
|
+
!!!
|
2
|
+
%html
|
3
|
+
%head
|
4
|
+
%title
|
5
|
+
Fukuzatsu:
|
6
|
+
= class_name
|
7
|
+
%style{media: "screen", type: "text/css"}
|
8
|
+
body { background: #593232; color: #fff; font-family: arial, sans-serif; padding: 2em; }
|
9
|
+
table { border: 10px solid #000; border-collapse: collapse; min-width: 50%; }
|
10
|
+
tr { border-top: 1px solid #000; }
|
11
|
+
tr.header { background: rgba(255, 255, 255, 0.75)}
|
12
|
+
tr.even { background: rgba(128, 128, 128, 0.5);}
|
13
|
+
tr.odd { background: rgba(128, 128, 128, 0.25);}
|
14
|
+
th { background: #000; text-align: left; padding: .5em; text-transform: uppercase; font-size: .8em}
|
15
|
+
td { text-align: left; padding: .5em;}
|
16
|
+
td.center { text-align: center; }
|
17
|
+
tfoot { background: #000; border-top: 10px solid #000; font-family: courier; margin-top: 4em; font-size: .75em; }
|
18
|
+
a:link, a:visited { color: #aaa }
|
19
|
+
h1 { color:#593232; font-size: 1.25em; }
|
20
|
+
h2 { color:#593232; font-size: .75em; margin-top: -1em}
|
21
|
+
%body
|
22
|
+
%table
|
23
|
+
%thead
|
24
|
+
%tr.header
|
25
|
+
%td{colspan: 3}
|
26
|
+
%h1
|
27
|
+
= class_name
|
28
|
+
%h2
|
29
|
+
= path_to_file
|
30
|
+
%tr
|
31
|
+
= header
|
32
|
+
%tbody
|
33
|
+
= rows
|
34
|
+
%tfoot
|
35
|
+
%tr
|
36
|
+
%td.center{colspan: 3}
|
37
|
+
%em
|
38
|
+
Analyzed on
|
39
|
+
= date
|
40
|
+
at
|
41
|
+
= time
|
42
|
+
by
|
43
|
+
%a{href: "https://gitlab.com/coraline/fukuzatsu", target: "_new"}
|
44
|
+
Fukuzatsu
|
@@ -0,0 +1,27 @@
|
|
1
|
+
module Formatters
|
2
|
+
|
3
|
+
class Text
|
4
|
+
|
5
|
+
include Formatters::Base
|
6
|
+
|
7
|
+
def header
|
8
|
+
"#{file.class_name}\t\t#{file.complexity}"
|
9
|
+
end
|
10
|
+
|
11
|
+
def export
|
12
|
+
puts content
|
13
|
+
end
|
14
|
+
|
15
|
+
def rows
|
16
|
+
file.methods.map do |method|
|
17
|
+
"#{file.class_name}\t#{method.prefix}#{method.name}\t#{method.complexity}"
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
def footer
|
22
|
+
end
|
23
|
+
|
24
|
+
end
|
25
|
+
|
26
|
+
end
|
27
|
+
|
@@ -0,0 +1,28 @@
|
|
1
|
+
class ParsedFile
|
2
|
+
|
3
|
+
include PoroPlus
|
4
|
+
include Ephemeral::Base
|
5
|
+
|
6
|
+
attr_accessor :path_to_file, :class_name
|
7
|
+
|
8
|
+
def class_name
|
9
|
+
@class_name ||= analyzer.extract_class_name
|
10
|
+
end
|
11
|
+
|
12
|
+
def content
|
13
|
+
@content ||= File.open(path_to_file, "r").read
|
14
|
+
end
|
15
|
+
|
16
|
+
def analyzer
|
17
|
+
Analyzer.new(content)
|
18
|
+
end
|
19
|
+
|
20
|
+
def complexity
|
21
|
+
@complexity ||= analyzer.complexity
|
22
|
+
end
|
23
|
+
|
24
|
+
def methods
|
25
|
+
@methods ||= analyzer.extract_methods
|
26
|
+
end
|
27
|
+
|
28
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
require 'parser/current'
|
2
|
+
|
3
|
+
class ParsedMethod
|
4
|
+
|
5
|
+
include PoroPlus
|
6
|
+
|
7
|
+
attr_accessor :name, :content, :type
|
8
|
+
|
9
|
+
def complexity
|
10
|
+
@complexity ||= Analyzer.new(content).complexity
|
11
|
+
end
|
12
|
+
|
13
|
+
def prefix
|
14
|
+
self.type == :class ? "." : "#"
|
15
|
+
end
|
16
|
+
|
17
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Analyzer do
|
4
|
+
|
5
|
+
let(:content_1) { File.read("spec/fixtures/program_1.rb") }
|
6
|
+
let(:content_2) { File.read("spec/fixtures/program_2.rb") }
|
7
|
+
|
8
|
+
context "program_1" do
|
9
|
+
|
10
|
+
let(:analyzer) { Analyzer.new(content_1) }
|
11
|
+
|
12
|
+
it "matches the manual calculation of complexity of 4" do
|
13
|
+
expect(analyzer.complexity).to eq(4)
|
14
|
+
end
|
15
|
+
|
16
|
+
end
|
17
|
+
|
18
|
+
context "program_2" do
|
19
|
+
|
20
|
+
let(:analyzer) { Analyzer.new(content_2) }
|
21
|
+
|
22
|
+
it "matches the manual calculation of complexity of 5" do
|
23
|
+
expect(analyzer.complexity).to eq(5)
|
24
|
+
end
|
25
|
+
|
26
|
+
end
|
27
|
+
|
28
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
def toppings
|
2
|
+
|
3
|
+
if rand(10) < 5
|
4
|
+
return "meatball"
|
5
|
+
end
|
6
|
+
|
7
|
+
if 1 < 5
|
8
|
+
return 'tomato'
|
9
|
+
else
|
10
|
+
if 4 ==5
|
11
|
+
if 2 > 0
|
12
|
+
return "parmesan"
|
13
|
+
else
|
14
|
+
return "romano"
|
15
|
+
end
|
16
|
+
else
|
17
|
+
if 2 > 0
|
18
|
+
return "alfredo"
|
19
|
+
else
|
20
|
+
return "gravy"
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
end
|
@@ -0,0 +1,66 @@
|
|
1
|
+
require 'fukuzatsu'
|
2
|
+
|
3
|
+
class Breathalizer
|
4
|
+
|
5
|
+
INDICATORS = [
|
6
|
+
:if,
|
7
|
+
:def,
|
8
|
+
]
|
9
|
+
|
10
|
+
attr_accessor :path_to_file, :edges, :nodes, :exits, :accumulator
|
11
|
+
|
12
|
+
def self.parse!(path_to_file)
|
13
|
+
new(path_to_file).parse
|
14
|
+
end
|
15
|
+
|
16
|
+
def initialize(path_to_file)
|
17
|
+
self.accumulator = []
|
18
|
+
self.edges = 0
|
19
|
+
self.nodes = 1
|
20
|
+
self.exits = 1
|
21
|
+
self.path_to_file = path_to_file
|
22
|
+
end
|
23
|
+
|
24
|
+
def file_contents
|
25
|
+
File.open(path_to_file, "r").read
|
26
|
+
end
|
27
|
+
|
28
|
+
def parsed
|
29
|
+
@parsed ||= Parser::CurrentRuby.parse(file_contents)
|
30
|
+
end
|
31
|
+
|
32
|
+
def parse!
|
33
|
+
traverse(parsed)
|
34
|
+
complexity
|
35
|
+
end
|
36
|
+
|
37
|
+
def traverse(node)#, accumulator=[])
|
38
|
+
|
39
|
+
accumulator << node.type
|
40
|
+
|
41
|
+
if node.type == :if || node.type == :begin
|
42
|
+
self.edges += 2
|
43
|
+
self.nodes += 2
|
44
|
+
self.exits += 1
|
45
|
+
elsif node.type == :def
|
46
|
+
self.edges += 1
|
47
|
+
self.nodes += 1
|
48
|
+
self.exits += 1
|
49
|
+
end
|
50
|
+
|
51
|
+
node.children.each do |child|
|
52
|
+
if child.respond_to?(:type) || child.respond_to?(:children)
|
53
|
+
accumulator << child.type
|
54
|
+
traverse(child, accumulator)
|
55
|
+
else
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
def complexity
|
61
|
+
p "edges = #{self.edges}, nodes = #{self.nodes}, exits = #{self.exits}"
|
62
|
+
self.edges - self.nodes + exits
|
63
|
+
end
|
64
|
+
|
65
|
+
end
|
66
|
+
|
@@ -0,0 +1,42 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Formatters::Csv do
|
4
|
+
|
5
|
+
let(:formatter) { Formatters::Csv.new }
|
6
|
+
|
7
|
+
describe "#header" do
|
8
|
+
it "outputs a header row" do
|
9
|
+
expect(formatter.header).to eq("class,method,complexity,lines")
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
describe "#rows" do
|
14
|
+
|
15
|
+
let(:expected) {
|
16
|
+
[
|
17
|
+
"Foo,baz,5,10",
|
18
|
+
"Foo,bat,15,100",
|
19
|
+
"Bar,gum,51,110",
|
20
|
+
"Bar,bun,55,101"
|
21
|
+
]
|
22
|
+
}
|
23
|
+
|
24
|
+
let(:file_1) { ParsedFile.new(class_name: "Foo") }
|
25
|
+
let(:file_2) { ParsedFile.new(class_name: "Bar") }
|
26
|
+
let(:method_1) { ParsedMethod.new(method_name: "baz", complexity: 5, loc: 10)}
|
27
|
+
let(:method_2) { ParsedMethod.new(method_name: "bat", complexity: 15, loc: 100)}
|
28
|
+
let(:method_3) { ParsedMethod.new(method_name: "gum", complexity: 51, loc: 110)}
|
29
|
+
let(:method_4) { ParsedMethod.new(method_name: "bun", complexity: 55, loc: 101)}
|
30
|
+
|
31
|
+
before do
|
32
|
+
file_1.parsed_methods = [method_1, method_2]
|
33
|
+
file_2.parsed_methods = [method_3, method_4]
|
34
|
+
formatter.parsed_files = [file_1, file_2]
|
35
|
+
end
|
36
|
+
|
37
|
+
it "builds an array" do
|
38
|
+
expect(formatter.rows).to eq(expected)
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
end
|
data/spec/spec_helper.rb
ADDED
metadata
ADDED
@@ -0,0 +1,202 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: fukuzatsu
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.9.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Bantik
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2014-07-02 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: require_all
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - ">="
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '0'
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - ">="
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '0'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: ephemeral
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - ">="
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '0'
|
34
|
+
type: :runtime
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - ">="
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '0'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: poro_plus
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - ">="
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '0'
|
48
|
+
type: :runtime
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - ">="
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '0'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: haml
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - ">="
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '0'
|
62
|
+
type: :runtime
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - ">="
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '0'
|
69
|
+
- !ruby/object:Gem::Dependency
|
70
|
+
name: parser
|
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'
|
83
|
+
- !ruby/object:Gem::Dependency
|
84
|
+
name: bundler
|
85
|
+
requirement: !ruby/object:Gem::Requirement
|
86
|
+
requirements:
|
87
|
+
- - "~>"
|
88
|
+
- !ruby/object:Gem::Version
|
89
|
+
version: '1.6'
|
90
|
+
type: :development
|
91
|
+
prerelease: false
|
92
|
+
version_requirements: !ruby/object:Gem::Requirement
|
93
|
+
requirements:
|
94
|
+
- - "~>"
|
95
|
+
- !ruby/object:Gem::Version
|
96
|
+
version: '1.6'
|
97
|
+
- !ruby/object:Gem::Dependency
|
98
|
+
name: rake
|
99
|
+
requirement: !ruby/object:Gem::Requirement
|
100
|
+
requirements:
|
101
|
+
- - ">="
|
102
|
+
- !ruby/object:Gem::Version
|
103
|
+
version: '0'
|
104
|
+
type: :development
|
105
|
+
prerelease: false
|
106
|
+
version_requirements: !ruby/object:Gem::Requirement
|
107
|
+
requirements:
|
108
|
+
- - ">="
|
109
|
+
- !ruby/object:Gem::Version
|
110
|
+
version: '0'
|
111
|
+
- !ruby/object:Gem::Dependency
|
112
|
+
name: rspec
|
113
|
+
requirement: !ruby/object:Gem::Requirement
|
114
|
+
requirements:
|
115
|
+
- - ">="
|
116
|
+
- !ruby/object:Gem::Version
|
117
|
+
version: '0'
|
118
|
+
type: :development
|
119
|
+
prerelease: false
|
120
|
+
version_requirements: !ruby/object:Gem::Requirement
|
121
|
+
requirements:
|
122
|
+
- - ">="
|
123
|
+
- !ruby/object:Gem::Version
|
124
|
+
version: '0'
|
125
|
+
- !ruby/object:Gem::Dependency
|
126
|
+
name: thor
|
127
|
+
requirement: !ruby/object:Gem::Requirement
|
128
|
+
requirements:
|
129
|
+
- - ">="
|
130
|
+
- !ruby/object:Gem::Version
|
131
|
+
version: '0'
|
132
|
+
type: :development
|
133
|
+
prerelease: false
|
134
|
+
version_requirements: !ruby/object:Gem::Requirement
|
135
|
+
requirements:
|
136
|
+
- - ">="
|
137
|
+
- !ruby/object:Gem::Version
|
138
|
+
version: '0'
|
139
|
+
description: Calculates the cyclomatic complexity of methods within a given file.
|
140
|
+
email:
|
141
|
+
- coraline@idolhands.com
|
142
|
+
executables:
|
143
|
+
- fuku
|
144
|
+
extensions: []
|
145
|
+
extra_rdoc_files: []
|
146
|
+
files:
|
147
|
+
- ".gitignore"
|
148
|
+
- ".rspec"
|
149
|
+
- Gemfile
|
150
|
+
- LICENSE.txt
|
151
|
+
- README.md
|
152
|
+
- Rakefile
|
153
|
+
- bin/fuku
|
154
|
+
- fukuzatsu.gemspec
|
155
|
+
- lib/fukuzatsu.rb
|
156
|
+
- lib/fukuzatsu/analyzer.rb
|
157
|
+
- lib/fukuzatsu/cli.rb
|
158
|
+
- lib/fukuzatsu/formatters/base.rb
|
159
|
+
- lib/fukuzatsu/formatters/csv.rb
|
160
|
+
- lib/fukuzatsu/formatters/html.rb
|
161
|
+
- lib/fukuzatsu/formatters/templates/output.html.haml
|
162
|
+
- lib/fukuzatsu/formatters/text.rb
|
163
|
+
- lib/fukuzatsu/parsed_file.rb
|
164
|
+
- lib/fukuzatsu/parsed_method.rb
|
165
|
+
- lib/fukuzatsu/version.rb
|
166
|
+
- spec/analyzer_spec.rb
|
167
|
+
- spec/fixtures/program_1.rb
|
168
|
+
- spec/fixtures/program_2.rb
|
169
|
+
- spec/fixtures/program_3.rb
|
170
|
+
- spec/formatters/csv_spec.rb
|
171
|
+
- spec/spec_helper.rb
|
172
|
+
homepage: https://gitlab.com/coraline/fukuzatsu
|
173
|
+
licenses:
|
174
|
+
- MIT
|
175
|
+
metadata: {}
|
176
|
+
post_install_message:
|
177
|
+
rdoc_options: []
|
178
|
+
require_paths:
|
179
|
+
- lib
|
180
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
181
|
+
requirements:
|
182
|
+
- - ">="
|
183
|
+
- !ruby/object:Gem::Version
|
184
|
+
version: '0'
|
185
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
186
|
+
requirements:
|
187
|
+
- - ">="
|
188
|
+
- !ruby/object:Gem::Version
|
189
|
+
version: '0'
|
190
|
+
requirements: []
|
191
|
+
rubyforge_project:
|
192
|
+
rubygems_version: 2.2.2
|
193
|
+
signing_key:
|
194
|
+
specification_version: 4
|
195
|
+
summary: A simple code complexity analyzer.
|
196
|
+
test_files:
|
197
|
+
- spec/analyzer_spec.rb
|
198
|
+
- spec/fixtures/program_1.rb
|
199
|
+
- spec/fixtures/program_2.rb
|
200
|
+
- spec/fixtures/program_3.rb
|
201
|
+
- spec/formatters/csv_spec.rb
|
202
|
+
- spec/spec_helper.rb
|