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.
@@ -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
@@ -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
@@ -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,17 @@
1
+ require 'stringio'
2
+
3
+ module Tuxedo
4
+ module Formatters
5
+ class BaseFormatter
6
+ attr_reader :output
7
+
8
+ def initialize(output=nil)
9
+ @output = output || StringIO.new
10
+ end
11
+
12
+ def format(metrics)
13
+ # NOP
14
+ end
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,13 @@
1
+ require 'tuxedo/formatters/base_formatter'
2
+
3
+ module Tuxedo
4
+ module Formatters
5
+ class BaseTextFormatter < BaseFormatter
6
+ include Tty
7
+
8
+ def message(message)
9
+ output.puts message
10
+ end
11
+ end
12
+ end
13
+ 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