sparqcode_cane 1.3.0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,135 @@
1
+ require 'optparse'
2
+ require 'cane/cli/translator'
3
+
4
+ module Cane
5
+ module CLI
6
+
7
+ # Provides a specification for the command line interface that drives
8
+ # documentation, parsing, and default values.
9
+ class Spec
10
+ DEFAULTS = {
11
+ abc_glob: '{app,lib}/**/*.rb',
12
+ abc_max: '15',
13
+ style_glob: '{app,lib,spec}/**/*.rb',
14
+ style_measure: '80',
15
+ doc_glob: '{app,lib}/**/*.rb',
16
+ max_violations: '0',
17
+ }
18
+
19
+ # Exception to indicate that no further processing is required and the
20
+ # program can exit. This is used to handle --help and --version flags.
21
+ class OptionsHandled < RuntimeError; end
22
+
23
+ def initialize
24
+ add_banner
25
+
26
+ add_abc_options
27
+ add_style_options
28
+ add_doc_options
29
+ add_threshold_options
30
+ add_cane_options
31
+
32
+ add_version
33
+ add_help
34
+ end
35
+
36
+ def parse(args)
37
+ parser.parse!(get_default_options + args)
38
+
39
+ Translator.new(options, DEFAULTS).to_hash
40
+ rescue OptionsHandled
41
+ nil
42
+ end
43
+
44
+ def get_default_options
45
+ if File.exists?('./.cane')
46
+ File.read('./.cane').gsub("\n", ' ').split(' ')
47
+ else
48
+ []
49
+ end
50
+ end
51
+
52
+ def add_banner
53
+ parser.banner = <<-BANNER
54
+ Usage: cane [options]
55
+
56
+ You can also put these options in a .cane file.
57
+
58
+ BANNER
59
+ end
60
+
61
+ def add_abc_options
62
+ add_option %w(--abc-glob GLOB), "Glob to run ABC metrics over"
63
+ add_option %w(--abc-max VALUE), "Ignore methods under this complexity"
64
+ add_option %w(--no-abc), "Disable ABC checking"
65
+
66
+ parser.separator ""
67
+ end
68
+
69
+ def add_style_options
70
+ add_option %w(--style-glob GLOB), "Glob to run style metrics over"
71
+ add_option %w(--style-measure VALUE), "Max line length"
72
+ add_option %w(--no-style), "Disable style checking"
73
+
74
+ parser.separator ""
75
+ end
76
+
77
+ def add_doc_options
78
+ add_option %w(--doc-glob GLOB), "Glob to run documentation checks over"
79
+ add_option %w(--no-doc), "Disable documentation checking"
80
+
81
+ parser.separator ""
82
+ end
83
+
84
+ def add_threshold_options
85
+ desc = "If FILE contains a number, verify it is >= to THRESHOLD."
86
+ parser.on("--gte FILE,THRESHOLD", Array, desc) do |opts|
87
+ (options[:threshold] ||= []) << opts.unshift(:>=)
88
+ end
89
+
90
+ parser.separator ""
91
+ end
92
+
93
+ def add_cane_options
94
+ add_option %w(--max-violations VALUE), "Max allowed violations"
95
+
96
+ parser.separator ""
97
+ end
98
+
99
+ def add_version
100
+ parser.on_tail("--version", "Show version") do
101
+ puts Cane::VERSION
102
+ raise OptionsHandled
103
+ end
104
+ end
105
+
106
+ def add_help
107
+ parser.on_tail("-h", "--help", "Show this message") do
108
+ puts parser
109
+ raise OptionsHandled
110
+ end
111
+ end
112
+
113
+ def add_option(option, description)
114
+ option_key = option[0].gsub('--', '').tr('-', '_').to_sym
115
+
116
+ if DEFAULTS.has_key?(option_key)
117
+ description += " (default: %s)" % DEFAULTS[option_key]
118
+ end
119
+
120
+ parser.on(option.join(' '), description) do |v|
121
+ options[option_key] = v
122
+ end
123
+ end
124
+
125
+ def options
126
+ @options ||= {}
127
+ end
128
+
129
+ def parser
130
+ @parser ||= OptionParser.new
131
+ end
132
+ end
133
+
134
+ end
135
+ end
@@ -0,0 +1,51 @@
1
+ module Cane
2
+ module CLI
3
+
4
+ # Translates CLI options with given defaults to a hash suitable to be
5
+ # passed to `Cane.run`.
6
+ class Translator < Struct.new(:options, :defaults)
7
+ def to_hash
8
+ result = {}
9
+ translate_abc_options(result)
10
+ translate_doc_options(result)
11
+ translate_style_options(result)
12
+
13
+ result[:threshold] = options.fetch(:threshold, [])
14
+ result[:max_violations] = option_with_default(:max_violations).to_i
15
+
16
+ result
17
+ end
18
+
19
+ def translate_abc_options(result)
20
+ result[:abc] = {
21
+ files: option_with_default(:abc_glob),
22
+ max: option_with_default(:abc_max).to_i
23
+ } unless check_disabled(:no_abc, [:abc_glob, :abc_max])
24
+ end
25
+
26
+ def translate_style_options(result)
27
+ result[:style] = {
28
+ files: option_with_default(:style_glob),
29
+ measure: option_with_default(:style_measure).to_i,
30
+ } unless check_disabled(:no_style, [:style_glob])
31
+ end
32
+
33
+ def translate_doc_options(result)
34
+ result[:doc] = {
35
+ files: option_with_default(:doc_glob),
36
+ } unless check_disabled(:no_doc, [:doc_glob])
37
+ end
38
+
39
+ def check_disabled(check, params)
40
+ relevant_options = options.keys & params + [check]
41
+
42
+ check == relevant_options[-1]
43
+ end
44
+
45
+ def option_with_default(key)
46
+ options.fetch(key, defaults.fetch(key))
47
+ end
48
+ end
49
+
50
+ end
51
+ end
@@ -0,0 +1,51 @@
1
+ module Cane
2
+
3
+ # Creates violations for class definitions that do not have an explantory
4
+ # comment immediately preceeding.
5
+ class DocCheck < Struct.new(:opts)
6
+ def violations
7
+ file_names.map { |file_name|
8
+ find_violations(file_name)
9
+ }.flatten
10
+ end
11
+
12
+ def find_violations(file_name)
13
+ last_line = ""
14
+ File.open(file_name, 'r:utf-8').lines.map.with_index do |line, number|
15
+ result = if class_definition?(line) && !comment?(last_line)
16
+ UndocumentedClassViolation.new(file_name, number + 1, line)
17
+ end
18
+ last_line = line
19
+ result
20
+ end.compact
21
+ end
22
+
23
+ def file_names
24
+ Dir[opts.fetch(:files)]
25
+ end
26
+
27
+ def class_definition?(line)
28
+ line =~ /^\s*class\s+/ and $'.index('<<') != 0
29
+ end
30
+
31
+ def comment?(line)
32
+ line =~ /^\s*#/
33
+ end
34
+ end
35
+
36
+ # Value object used by DocCheck.
37
+ class UndocumentedClassViolation < Struct.new(:file_name, :number, :line)
38
+ def description
39
+ "Classes are not documented"
40
+ end
41
+
42
+ def columns
43
+ ["%s:%i" % [file_name, number], extract_class_name(line)]
44
+ end
45
+
46
+ def extract_class_name(line)
47
+ line.match(/class (\S+)/)[1]
48
+ end
49
+ end
50
+
51
+ end
@@ -0,0 +1,82 @@
1
+ require 'rake'
2
+ require 'rake/tasklib'
3
+
4
+ module Cane
5
+ # Creates a rake task to run cane with given configuration.
6
+ #
7
+ # Examples
8
+ #
9
+ # desc "Run code quality checks"
10
+ # Cane::RakeTask.new(:quality) do |cane|
11
+ # cane.abc_max = 10
12
+ # cane.doc_glob = 'lib/**/*.rb'
13
+ # cane.no_style = true
14
+ # cane.add_threshold 'coverage/covered_percent', :>=, 99
15
+ # end
16
+ class RakeTask < ::Rake::TaskLib
17
+ attr_accessor :name
18
+
19
+ # Glob to run ABC metrics over (default: "lib/**/*.rb")
20
+ attr_accessor :abc_glob
21
+ # Max complexity of methods to allow (default: 15)
22
+ attr_accessor :abc_max
23
+ # Glob to run style checks over (default: "{lib,spec}/**/*.rb")
24
+ attr_accessor :style_glob
25
+ # TRUE to disable style checks
26
+ attr_accessor :no_style
27
+ # Max line length (default: 80)
28
+ attr_accessor :style_measure
29
+ # Glob to run doc checks over (default: "lib/**/*.rb")
30
+ attr_accessor :doc_glob
31
+ # TRUE to disable doc checks
32
+ attr_accessor :no_doc
33
+ # Max violations to tolerate (default: 0)
34
+ attr_accessor :max_violations
35
+
36
+ # Add a threshold check. If the file exists and it contains a number,
37
+ # compare that number with the given value using the operator.
38
+ def add_threshold(file, operator, value)
39
+ @threshold << [operator, file, value]
40
+ end
41
+
42
+ def initialize(task_name = nil)
43
+ self.name = task_name || :cane
44
+ @threshold = []
45
+ yield self if block_given?
46
+
47
+ unless ::Rake.application.last_comment
48
+ desc %(Check code quality metrics with cane)
49
+ end
50
+
51
+ task name do
52
+ require 'cane/cli'
53
+ abort unless Cane.run(translated_options)
54
+ end
55
+ end
56
+
57
+ def options
58
+ [
59
+ :abc_glob,
60
+ :abc_max,
61
+ :doc_glob,
62
+ :no_doc,
63
+ :max_violations,
64
+ :style_glob,
65
+ :no_style,
66
+ :style_measure
67
+ ].inject(threshold: @threshold) do |opts, setting|
68
+ value = self.send(setting)
69
+ opts[setting] = value unless value.nil?
70
+ opts
71
+ end
72
+ end
73
+
74
+ def default_options
75
+ Cane::CLI::Spec::DEFAULTS
76
+ end
77
+
78
+ def translated_options
79
+ Cane::CLI::Translator.new(options, default_options).to_hash
80
+ end
81
+ end
82
+ end
@@ -0,0 +1,45 @@
1
+ require 'cane/style_violation'
2
+
3
+ module Cane
4
+
5
+ # Creates violations for files that do not meet style conventions. Only
6
+ # highly obvious, probable, and non-controversial checks are performed here.
7
+ # It is not the goal of the tool to provide an extensive style report, but
8
+ # only to prevent studid mistakes.
9
+ class StyleCheck < Struct.new(:opts)
10
+ def violations
11
+ file_list.map do |file_path|
12
+ map_lines(file_path) do |line, line_number|
13
+ violations_for_line(line.chomp).map do |message|
14
+ StyleViolation.new(file_path, line_number + 1, message)
15
+ end
16
+ end
17
+ end.flatten
18
+ end
19
+
20
+ protected
21
+
22
+ def violations_for_line(line)
23
+ result = []
24
+ if line.length > measure
25
+ result << "Line is >%i characters (%i)" % [measure, line.length]
26
+ end
27
+ result << "Line contains trailing whitespace" if line =~ /\s$/
28
+ result << "Line contains hard tabs" if line =~ /\t/
29
+ result
30
+ end
31
+
32
+ def file_list
33
+ Dir[opts.fetch(:files)]
34
+ end
35
+
36
+ def measure
37
+ opts.fetch(:measure)
38
+ end
39
+
40
+ def map_lines(file_path, &block)
41
+ File.open(file_path).each_line.map.with_index(&block)
42
+ end
43
+ end
44
+
45
+ end
@@ -0,0 +1,10 @@
1
+ # Value object used by StyleCheck.
2
+ class StyleViolation < Struct.new(:file_name, :line, :message)
3
+ def description
4
+ "Lines violated style requirements"
5
+ end
6
+
7
+ def columns
8
+ ["%s:%i" % [file_name, line], message]
9
+ end
10
+ end
@@ -0,0 +1,20 @@
1
+ module Cane
2
+
3
+ # Value object used by AbcCheck for a file that cannot be parsed. This is
4
+ # handled by AbcCheck rather than a separate class since it is a low value
5
+ # violation (syntax errors should have been picked up by specs) but we still
6
+ # have to deal with the edge case.
7
+ class SyntaxViolation < Struct.new(:file_name)
8
+ def columns
9
+ [file_name]
10
+ end
11
+
12
+ def description
13
+ "Files contained invalid syntax"
14
+ end
15
+
16
+ def sort_index
17
+ 0
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,30 @@
1
+ require 'cane/threshold_violation'
2
+
3
+ # Configurable check that allows the contents of a file to be compared against
4
+ # a given value.
5
+ class ThresholdCheck < Struct.new(:checks)
6
+ def violations
7
+ checks.map do |operator, file, limit|
8
+ value = value_from_file(file)
9
+
10
+ unless value.send(operator, limit.to_f)
11
+ ThresholdViolation.new(file, operator, value, limit)
12
+ end
13
+ end.compact
14
+ end
15
+
16
+ def value_from_file(file)
17
+ begin
18
+ contents = File.read(file).chomp.to_f
19
+ rescue Errno::ENOENT
20
+ UnavailableValue.new
21
+ end
22
+ end
23
+
24
+ # Null object for all cases when the value to be compared against cannot be
25
+ # read.
26
+ class UnavailableValue
27
+ def >=(_); false end
28
+ def to_s; 'unavailable' end
29
+ end
30
+ end
@@ -0,0 +1,15 @@
1
+ # Value object used by ThresholdCheck.
2
+ class ThresholdViolation < Struct.new(:name, :operator, :value, :limit)
3
+ def description
4
+ "Quality threshold crossed"
5
+ end
6
+
7
+ def columns
8
+ ["%s is %s, should be %s %s" % [
9
+ name,
10
+ value,
11
+ operator,
12
+ limit
13
+ ]]
14
+ end
15
+ end