tuxedo 0.9.2
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 +2 -0
- data/.rspec +2 -0
- data/.rvmrc +1 -0
- data/Gemfile +3 -0
- data/Gemfile.lock +41 -0
- data/README.md +5 -0
- data/Rakefile +30 -0
- data/bin/tuxedo +4 -0
- data/data/sample_cane_output.txt +32 -0
- data/data/sample_reek_output.yml +534 -0
- data/data/sample_reek_output_clean.yml +534 -0
- data/lib/tuxedo.rb +26 -0
- data/lib/tuxedo/cane_parser.rb +64 -0
- data/lib/tuxedo/cane_violation.rb +31 -0
- data/lib/tuxedo/cane_violation/abc_max_violation.rb +37 -0
- data/lib/tuxedo/cane_violation/style_violation.rb +40 -0
- data/lib/tuxedo/cane_violation/syntax_violation.rb +27 -0
- data/lib/tuxedo/cane_violation/threshold_violation.rb +33 -0
- data/lib/tuxedo/cane_violation/undocumented_class_violation.rb +35 -0
- data/lib/tuxedo/cli.rb +18 -0
- data/lib/tuxedo/error.rb +15 -0
- data/lib/tuxedo/formatters/base_formatter.rb +17 -0
- data/lib/tuxedo/formatters/base_text_formatter.rb +13 -0
- data/lib/tuxedo/formatters/cane_text_formatter.rb +68 -0
- data/lib/tuxedo/outputter.rb +16 -0
- data/lib/tuxedo/rake_task.rb +29 -0
- data/lib/tuxedo/reek_parser.rb +54 -0
- data/lib/tuxedo/runner.rb +64 -0
- data/lib/tuxedo/tty.rb +116 -0
- data/lib/tuxedo/version.rb +3 -0
- data/spec/fabricators/cane_output_fabricator.rb +0 -0
- data/spec/helpers/cane_output_helper.rb +42 -0
- data/spec/helpers/reek_output_helper.rb +613 -0
- data/spec/spec_helper.rb +16 -0
- data/spec/tuxedo/cane_parser_spec.rb +63 -0
- data/spec/tuxedo/reek_parser_spec.rb +49 -0
- data/tuxedo.gemspec +32 -0
- metadata +173 -0
data/lib/tuxedo.rb
ADDED
@@ -0,0 +1,26 @@
|
|
1
|
+
$LOAD_PATH.unshift(File.expand_path(File.dirname(__FILE__))) unless
|
2
|
+
$LOAD_PATH.include?(File.expand_path(File.dirname(__FILE__)))
|
3
|
+
|
4
|
+
require 'yaml'
|
5
|
+
require 'Open3'
|
6
|
+
require 'tuxedo/outputter'
|
7
|
+
require 'tuxedo/cane_parser'
|
8
|
+
require 'tuxedo/reek_parser'
|
9
|
+
require 'tuxedo/error'
|
10
|
+
require 'tuxedo/runner'
|
11
|
+
require 'tuxedo/tty'
|
12
|
+
require 'tuxedo/formatters/cane_text_formatter'
|
13
|
+
|
14
|
+
module Tuxedo
|
15
|
+
def self.output_to_console
|
16
|
+
cane_violations = Runner.run_cane
|
17
|
+
formatter = Formatters::CaneTextFormatter.new($stdout)
|
18
|
+
formatter.format(cane_violations)
|
19
|
+
puts ""
|
20
|
+
|
21
|
+
rp = Tuxedo::ReekParser.new
|
22
|
+
reek_output = Tuxedo::Runner.run_reek
|
23
|
+
rp.parse_reek(reek_output)
|
24
|
+
Tuxedo::Outputter.print_to_screen(rp.result) if rp.result.is_a? Hash
|
25
|
+
end
|
26
|
+
end
|
@@ -0,0 +1,64 @@
|
|
1
|
+
module Tuxedo
|
2
|
+
class CaneParser
|
3
|
+
attr_accessor :result
|
4
|
+
|
5
|
+
def initialize
|
6
|
+
self.result = { }
|
7
|
+
end
|
8
|
+
|
9
|
+
def parse_cane(output)
|
10
|
+
output.split("\n").each do |line|
|
11
|
+
if has_error?(line)
|
12
|
+
error = process_line(line)
|
13
|
+
push_error(error)
|
14
|
+
end
|
15
|
+
end
|
16
|
+
return self
|
17
|
+
end
|
18
|
+
|
19
|
+
def push_error(error)
|
20
|
+
if self.result[ error.name.to_sym ]
|
21
|
+
self.result[ error.name.to_sym ] << error
|
22
|
+
else
|
23
|
+
self.result[ error.name.to_sym ] = [ error ]
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
def process_line(line)
|
28
|
+
Tuxedo::Error.new( :name => get_error(line),
|
29
|
+
:source => get_file(line),
|
30
|
+
:line => get_lines(line))
|
31
|
+
end
|
32
|
+
|
33
|
+
def has_error?(line)
|
34
|
+
line.include?(">") || line.include?("Line contains trailing whitespaces")
|
35
|
+
end
|
36
|
+
|
37
|
+
def get_error(line)
|
38
|
+
if line.include?("Line is >80 characters")
|
39
|
+
error = "Line is >80 characters"
|
40
|
+
elsif line.include?("Line contains trailing whitespaces")
|
41
|
+
error = "Line contains trailing whitespaces"
|
42
|
+
elsif line.include?(">") # abc complexity errors have a > char too
|
43
|
+
error = "Maximum allowed ABC complexity"
|
44
|
+
end
|
45
|
+
error
|
46
|
+
end
|
47
|
+
|
48
|
+
def get_file(line)
|
49
|
+
if line.include?(":")
|
50
|
+
line.match(/([a-zA-Z0-9.\/_]+):/)[1]
|
51
|
+
else
|
52
|
+
line.split(" ").first
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
def get_lines(line)
|
57
|
+
if line.include?(":")
|
58
|
+
[ line.match(/:(\d+)/)[1].to_i ]
|
59
|
+
else
|
60
|
+
[ line.split(" ").last.to_i ]
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
require 'tuxedo/cane_violation/abc_max_violation'
|
2
|
+
require 'tuxedo/cane_violation/undocumented_class_violation'
|
3
|
+
require 'tuxedo/cane_violation/style_violation'
|
4
|
+
require 'tuxedo/cane_violation/syntax_violation'
|
5
|
+
require 'tuxedo/cane_violation/threshold_violation'
|
6
|
+
|
7
|
+
module Tuxedo
|
8
|
+
class CaneViolation
|
9
|
+
def self.from_cane(cane_violation)
|
10
|
+
type = cane_violation.class.name.split('::').last
|
11
|
+
klazz = const_defined?(type) && const_get(type)
|
12
|
+
klazz && klazz.from_cane(cane_violation)
|
13
|
+
end
|
14
|
+
|
15
|
+
def violation_type
|
16
|
+
self.class.name.split("::").last
|
17
|
+
end
|
18
|
+
|
19
|
+
def violation_type_snake_cased
|
20
|
+
_underscore(violation_type)
|
21
|
+
end
|
22
|
+
|
23
|
+
private
|
24
|
+
|
25
|
+
def _underscore(string)
|
26
|
+
string = string.gsub(/([A-Z\d]+)([A-Z][a-z])/,'\1_\2')
|
27
|
+
string = string.gsub(/([a-z\d])([A-Z])/,'\1_\2')
|
28
|
+
string.downcase
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
module Tuxedo
|
2
|
+
class CaneViolation
|
3
|
+
class AbcMaxViolation < CaneViolation
|
4
|
+
attr_accessor :file_name, :detail, :complexity
|
5
|
+
attr_accessor :class_name, :method_name, :expression
|
6
|
+
|
7
|
+
def self.from_cane(cane_violation)
|
8
|
+
violation = new
|
9
|
+
|
10
|
+
violation.file_name = cane_violation.file_name
|
11
|
+
violation.detail = cane_violation.detail
|
12
|
+
violation.complexity = cane_violation.complexity
|
13
|
+
|
14
|
+
parts = violation.detail.split(" > ")
|
15
|
+
violation.class_name = parts[-2]
|
16
|
+
violation.method_name = parts[-1]
|
17
|
+
violation.expression = parts[0..-2].join("::") + "." + parts[-1]
|
18
|
+
|
19
|
+
violation
|
20
|
+
end
|
21
|
+
|
22
|
+
def description
|
23
|
+
"Methods exceeded maximum allowed ABC complexity"
|
24
|
+
end
|
25
|
+
|
26
|
+
def to_hash
|
27
|
+
{
|
28
|
+
:violation_type => violation_type,
|
29
|
+
:file_name => file_name,
|
30
|
+
:detail => detail,
|
31
|
+
:complexity => complexity,
|
32
|
+
:description => description,
|
33
|
+
}
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
@@ -0,0 +1,40 @@
|
|
1
|
+
module Tuxedo
|
2
|
+
class CaneViolation
|
3
|
+
class StyleViolation < CaneViolation
|
4
|
+
attr_accessor :file_name, :line_number, :message
|
5
|
+
attr_accessor :style_violation_type, :max_char_count, :char_count
|
6
|
+
|
7
|
+
def self.from_cane(cane_violation)
|
8
|
+
violation = new
|
9
|
+
|
10
|
+
violation.file_name = cane_violation.file_name
|
11
|
+
violation.line_number = cane_violation.line
|
12
|
+
violation.message = cane_violation.message
|
13
|
+
|
14
|
+
if cane_violation.message.match /Line is >(\d+) characters \((\d+)\)/
|
15
|
+
violation.style_violation_type = "LineTooLong"
|
16
|
+
violation.max_char_count = $1.to_i
|
17
|
+
violation.char_count = $2.to_i
|
18
|
+
end
|
19
|
+
|
20
|
+
violation
|
21
|
+
end
|
22
|
+
|
23
|
+
def description
|
24
|
+
"Lines violated style requirements"
|
25
|
+
end
|
26
|
+
|
27
|
+
def to_hash
|
28
|
+
{
|
29
|
+
:violation_type => violation_type,
|
30
|
+
:file_name => file_name,
|
31
|
+
:line => line,
|
32
|
+
:style_violation_type => style_violation_type,
|
33
|
+
:max_char_count => max_char_count,
|
34
|
+
:char_count => char_count,
|
35
|
+
:description => description,
|
36
|
+
}
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
module Tuxedo
|
2
|
+
class CaneViolation
|
3
|
+
class SyntaxViolation < CaneViolation
|
4
|
+
attr_accessor :file_name
|
5
|
+
|
6
|
+
def self.from_cane(cane_violation)
|
7
|
+
violation = new
|
8
|
+
|
9
|
+
violation.file_name = cane_violation.file_name
|
10
|
+
|
11
|
+
violation
|
12
|
+
end
|
13
|
+
|
14
|
+
def description
|
15
|
+
"Files contained invalid syntax"
|
16
|
+
end
|
17
|
+
|
18
|
+
def to_hash
|
19
|
+
{
|
20
|
+
:violation_type => violation_type,
|
21
|
+
:file_name => file_name,
|
22
|
+
:description => description,
|
23
|
+
}
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
module Tuxedo
|
2
|
+
class CaneViolation
|
3
|
+
class ThresholdViolation < CaneViolation
|
4
|
+
attr_accessor :name, :operator, :value, :limit
|
5
|
+
|
6
|
+
def self.from_cane(cane_violation)
|
7
|
+
violation = new
|
8
|
+
|
9
|
+
violation.name = cane_violation.name
|
10
|
+
violation.operator = cane_violation.operator
|
11
|
+
violation.value = cane_violation.value
|
12
|
+
violation.limit = cane_violation.limit
|
13
|
+
|
14
|
+
violation
|
15
|
+
end
|
16
|
+
|
17
|
+
def description
|
18
|
+
"Quality threshold crossed"
|
19
|
+
end
|
20
|
+
|
21
|
+
def to_hash
|
22
|
+
{
|
23
|
+
:violation_type => violation_type,
|
24
|
+
:name => name,
|
25
|
+
:operator => operator,
|
26
|
+
:value => value,
|
27
|
+
:limit => limit,
|
28
|
+
:description => description,
|
29
|
+
}
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
module Tuxedo
|
2
|
+
class CaneViolation
|
3
|
+
class UndocumentedClassViolation < CaneViolation
|
4
|
+
attr_accessor :file_name, :line_number, :line
|
5
|
+
|
6
|
+
def self.from_cane(cane_violation)
|
7
|
+
violation = new
|
8
|
+
|
9
|
+
violation.file_name = cane_violation.file_name
|
10
|
+
violation.line_number = cane_violation.number
|
11
|
+
violation.line = cane_violation.line
|
12
|
+
|
13
|
+
violation
|
14
|
+
end
|
15
|
+
|
16
|
+
def description
|
17
|
+
"Classes are not documented"
|
18
|
+
end
|
19
|
+
|
20
|
+
def class_name(line)
|
21
|
+
line.match(/class (\S+)/)[1]
|
22
|
+
end
|
23
|
+
|
24
|
+
def to_hash
|
25
|
+
{
|
26
|
+
:violation_type => violation_type,
|
27
|
+
:file_name => file_name,
|
28
|
+
:line_number => line_number,
|
29
|
+
:line => line,
|
30
|
+
:description => description,
|
31
|
+
}
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
data/lib/tuxedo/cli.rb
ADDED
@@ -0,0 +1,18 @@
|
|
1
|
+
module Tuxedo
|
2
|
+
module Cli extend self
|
3
|
+
class CliResult < Struct.new(:stdout, :stderr, :exit_status)
|
4
|
+
def exit_code
|
5
|
+
exit_status.exitstatus
|
6
|
+
end
|
7
|
+
end
|
8
|
+
|
9
|
+
def sh(cmd)
|
10
|
+
Open3.popen3(cmd) do |stdin, stdout, stderr, wait_thr|
|
11
|
+
pid = wait_thr.pid # pid of the started process.
|
12
|
+
exit_status = wait_thr.value
|
13
|
+
|
14
|
+
CliResult.new(stdout.read, stderr.read, exit_status)
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
data/lib/tuxedo/error.rb
ADDED
@@ -0,0 +1,15 @@
|
|
1
|
+
module Tuxedo
|
2
|
+
class Error
|
3
|
+
attr_accessor :name, :source, :line
|
4
|
+
|
5
|
+
def initialize(params)
|
6
|
+
self.name = params[:name]
|
7
|
+
self.source = params[:source]
|
8
|
+
self.line = params[:line]
|
9
|
+
end
|
10
|
+
|
11
|
+
def print
|
12
|
+
"#{self.source} #{self.line}"
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
@@ -0,0 +1,68 @@
|
|
1
|
+
require 'tuxedo/formatters/base_text_formatter'
|
2
|
+
|
3
|
+
module Tuxedo
|
4
|
+
module Formatters
|
5
|
+
class CaneTextFormatter < BaseTextFormatter
|
6
|
+
def format(violations)
|
7
|
+
with_color(STDOUT.tty?) do
|
8
|
+
format_violations_of_type(violations, "AbcMaxViolation")
|
9
|
+
format_style_violations(violations)
|
10
|
+
format_violations_of_type(violations, "SyntaxViolation")
|
11
|
+
format_violations_of_type(violations, "ThresholdViolation")
|
12
|
+
format_violations_of_type(violations, "UndocumentedClassViolation", :line_number)
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
private
|
17
|
+
|
18
|
+
def format_violations_of_type(violations, type, selector=nil)
|
19
|
+
violations = violations.select {|v| v.violation_type == type}.group_by(&:file_name)
|
20
|
+
|
21
|
+
if violations.any?
|
22
|
+
message yellow type
|
23
|
+
message_separator
|
24
|
+
|
25
|
+
format_grouped_by_file_name(violations, selector)
|
26
|
+
message ""
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
def format_style_violations(violations)
|
31
|
+
# Too Long Lines
|
32
|
+
style_violations = violations.select {|v| v.violation_type == "StyleViolation"}
|
33
|
+
too_long_lines =
|
34
|
+
style_violations.
|
35
|
+
select {|v| v.style_violation_type == "LineTooLong"}.
|
36
|
+
group_by(&:file_name)
|
37
|
+
|
38
|
+
if too_long_lines.any?
|
39
|
+
max = too_long_lines.values.first.first.max_char_count
|
40
|
+
message yellow "Line is >#{max} characters"
|
41
|
+
message_separator
|
42
|
+
format_grouped_by_file_name(too_long_lines, :line_number)
|
43
|
+
message ""
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
def message_separator
|
48
|
+
message green "------------------------------------"
|
49
|
+
end
|
50
|
+
|
51
|
+
def format_grouped_by_file_name(violations, selector=nil)
|
52
|
+
return unless violations.any?
|
53
|
+
|
54
|
+
if selector
|
55
|
+
violations.each do |file, vs|
|
56
|
+
puts violations.to_a if vs.nil?
|
57
|
+
lines = vs.map {|v| green v.send(selector)}.join(", ")
|
58
|
+
message "#{file} [#{lines}]"
|
59
|
+
end
|
60
|
+
else
|
61
|
+
violations.each do |file, vs|
|
62
|
+
message file
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
module Tuxedo
|
2
|
+
class Outputter
|
3
|
+
def self.print_to_screen(error_collection)
|
4
|
+
#errors will be hash of errors
|
5
|
+
|
6
|
+
error_collection.each do |error_type, errors|
|
7
|
+
puts error_type
|
8
|
+
puts "------------------------------------"
|
9
|
+
errors.each do |error|
|
10
|
+
puts error.print
|
11
|
+
end
|
12
|
+
puts
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|