cane 2.0.0 → 2.1.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.
- data/HISTORY.md +13 -1
- data/README.md +53 -7
- data/cane.gemspec +1 -0
- data/lib/cane.rb +3 -3
- data/lib/cane/cli.rb +5 -5
- data/lib/cane/cli/options.rb +28 -0
- data/lib/cane/cli/{spec.rb → parser.rb} +39 -27
- data/lib/cane/default_checks.rb +16 -0
- data/lib/cane/doc_check.rb +1 -1
- data/lib/cane/encoding_aware_iterator.rb +17 -22
- data/lib/cane/file.rb +4 -0
- data/lib/cane/rake_task.rb +20 -14
- data/lib/cane/style_check.rb +1 -1
- data/lib/cane/version.rb +1 -1
- data/spec/cane_spec.rb +46 -137
- data/spec/cli_spec.rb +24 -0
- data/spec/encoding_aware_iterator_spec.rb +11 -2
- data/spec/parser_spec.rb +89 -0
- data/spec/rake_task_spec.rb +27 -4
- data/spec/runner_spec.rb +12 -0
- data/spec/spec_helper.rb +16 -10
- data/spec/style_check_spec.rb +14 -2
- metadata +24 -20
data/HISTORY.md
CHANGED
@@ -1,17 +1,29 @@
|
|
1
1
|
# Cane History
|
2
2
|
|
3
|
+
## 2.1.0 - 26 August 2012 (2962d8fb)
|
4
|
+
|
5
|
+
* Support for user-defined checks (#30)
|
6
|
+
|
3
7
|
## 2.0.0 - 19 August 2012 (35cae086)
|
4
8
|
|
5
9
|
* ABC check labels `MyClass = Struct.new {}` and `Class.new` correctly (#20)
|
6
10
|
* Magic comments (`# encoding: utf-8`) are not recognized as appropriate class documentation (#21)
|
7
11
|
* Invalid UTF-8 is handled correctly (#22)
|
8
12
|
* Gracefully handle unknown options
|
9
|
-
* ABC check output uses a standard format (Foo::Bar#method rather than Foo > Bar > method)
|
13
|
+
* ABC check output uses a standard format (`Foo::Bar#method` rather than `Foo > Bar > method`)
|
10
14
|
* **BREAKING** Add `--abc-exclude`, `--style-exclude` CLI flags, remove YAML support
|
11
15
|
* **BREAKING-INTERNAL** Use hashes rather than explicit violation classes
|
12
16
|
* **BREAKING-INTERNAL** Remove translator class, pass CLI args direct to checks
|
13
17
|
* **INTERNAL** Wiring in a new check only requires changing one file (#15)
|
14
18
|
|
19
|
+
This snippet will convert your YAML exclusions file to the new CLI syntax:
|
20
|
+
|
21
|
+
y = YAML.load(File.read('exclusions.yml'))
|
22
|
+
puts (
|
23
|
+
y.fetch('abc', []).map {|x| %|--abc-exclude "#{x}"| } +
|
24
|
+
y.fetch('style', []).map {|x| %|--style-exclude "#{x}"| }
|
25
|
+
).join("\n")
|
26
|
+
|
15
27
|
## 1.4.0 - 2 July 2012 (1afc999d)
|
16
28
|
|
17
29
|
* Allow files and methods to be whitelisted (#16)
|
data/README.md
CHANGED
@@ -32,7 +32,10 @@ Customize behaviour with a wealth of options:
|
|
32
32
|
> cane --help
|
33
33
|
Usage: cane [options]
|
34
34
|
|
35
|
-
|
35
|
+
Default options are loaded from a .cane file in the current directory.
|
36
|
+
|
37
|
+
-r, --require FILE Load a Ruby file containing user-defined checks
|
38
|
+
-c, --check CLASS Use the given user-defined check
|
36
39
|
|
37
40
|
--abc-glob GLOB Glob to run ABC metrics over (default: {app,lib}/**/*.rb)
|
38
41
|
--abc-max VALUE Ignore methods under this complexity (default: 15)
|
@@ -54,16 +57,15 @@ Customize behaviour with a wealth of options:
|
|
54
57
|
-v, --version Show version
|
55
58
|
-h, --help Show this message
|
56
59
|
|
57
|
-
Set default options
|
60
|
+
Set default options using a `.cane` file:
|
58
61
|
|
59
62
|
> cat .cane
|
60
63
|
--no-doc
|
61
64
|
--abc-glob **/*.rb
|
62
65
|
> cane
|
63
66
|
|
64
|
-
It works
|
65
|
-
|
66
|
-
> cane --no-doc --abc-glob '**/*.rb'
|
67
|
+
It works exactly the same as specifying the options on the command-line.
|
68
|
+
Command-line arguments will override arguments specified in the `.cane` file.
|
67
69
|
|
68
70
|
## Integrating with Rake
|
69
71
|
|
@@ -94,7 +96,9 @@ on to an existing application that may already have many violations. By setting
|
|
94
96
|
the maximum to the current number, no immediate changes will be required to
|
95
97
|
your existing code base, but you will be protected from things getting worse.
|
96
98
|
|
97
|
-
You
|
99
|
+
You may also consider beginning with high thresholds and ratcheting them down
|
100
|
+
over time, or defining exclusions for specific troublesome violations (not
|
101
|
+
recommended).
|
98
102
|
|
99
103
|
## Integrating with SimpleCov
|
100
104
|
|
@@ -120,12 +124,54 @@ You can use a `SimpleCov` formatter to create the required file:
|
|
120
124
|
|
121
125
|
SimpleCov.formatter = SimpleCov::Formatter::QualityFormatter
|
122
126
|
|
127
|
+
## Implementing your own checks
|
128
|
+
|
129
|
+
Checks must implement:
|
130
|
+
|
131
|
+
* A class level `options` method that returns a hash of available options. This
|
132
|
+
will be included in help output if the check is added before `--help`. If
|
133
|
+
your check does not require any configuration, return an empty hash.
|
134
|
+
* A one argument constructor, into which will be passed the options specified
|
135
|
+
for your check.
|
136
|
+
* A `violations` method that returns an array of violations.
|
137
|
+
|
138
|
+
See existing checks for guidance. Create your check in a new file:
|
139
|
+
|
140
|
+
# unhappy.rb
|
141
|
+
class UnhappyCheck < Struct.new(:opts)
|
142
|
+
def self.options
|
143
|
+
{
|
144
|
+
unhappy_file: ["File to check", default: [nil]]
|
145
|
+
}
|
146
|
+
end
|
147
|
+
|
148
|
+
def violations
|
149
|
+
[
|
150
|
+
description: "Files are unhappy",
|
151
|
+
file: opts.fetch(:unhappy_file),
|
152
|
+
label: ":("
|
153
|
+
]
|
154
|
+
end
|
155
|
+
end
|
156
|
+
|
157
|
+
Include your check either using command-line options:
|
158
|
+
|
159
|
+
cane -r unhappy.rb --check UnhappyCheck --unhappy-file myfile
|
160
|
+
|
161
|
+
Or in your rake task:
|
162
|
+
|
163
|
+
require 'unhappy'
|
164
|
+
|
165
|
+
Cane::RakeTask.new(:quality) do |c|
|
166
|
+
c.use UnhappyCheck, unhappy_file: 'myfile'
|
167
|
+
end
|
168
|
+
|
123
169
|
## Compatibility
|
124
170
|
|
125
171
|
Requires MRI 1.9, since it depends on the `ripper` library to calculate
|
126
172
|
complexity metrics. This only applies to the Ruby used to run Cane, not the
|
127
173
|
project it is being run against. In other words, you can run Cane against your
|
128
|
-
1.8 project.
|
174
|
+
1.8 or JRuby project.
|
129
175
|
|
130
176
|
## Support
|
131
177
|
|
data/cane.gemspec
CHANGED
data/lib/cane.rb
CHANGED
@@ -10,9 +10,9 @@ module Cane
|
|
10
10
|
# hands the result to a formatter for display. This is the core of the
|
11
11
|
# application, but for the actual entry point see `Cane::CLI`.
|
12
12
|
class Runner
|
13
|
-
def initialize(
|
14
|
-
@opts =
|
15
|
-
@checks = checks
|
13
|
+
def initialize(spec)
|
14
|
+
@opts = spec
|
15
|
+
@checks = spec[:checks]
|
16
16
|
end
|
17
17
|
|
18
18
|
def run
|
data/lib/cane/cli.rb
CHANGED
@@ -1,16 +1,16 @@
|
|
1
1
|
require 'cane'
|
2
2
|
require 'cane/version'
|
3
3
|
|
4
|
-
require 'cane/cli/
|
4
|
+
require 'cane/cli/parser'
|
5
5
|
|
6
6
|
module Cane
|
7
7
|
module CLI
|
8
8
|
def run(args)
|
9
|
-
|
10
|
-
if
|
11
|
-
Cane.run(
|
9
|
+
spec = Parser.parse(args)
|
10
|
+
if spec.is_a?(Hash)
|
11
|
+
Cane.run(spec)
|
12
12
|
else
|
13
|
-
|
13
|
+
spec
|
14
14
|
end
|
15
15
|
end
|
16
16
|
module_function :run
|
@@ -0,0 +1,28 @@
|
|
1
|
+
require 'cane/default_checks'
|
2
|
+
|
3
|
+
module Cane
|
4
|
+
module CLI
|
5
|
+
def defaults(check)
|
6
|
+
check.options.each_with_object({}) {|(k, v), h|
|
7
|
+
option_opts = v[1] || {}
|
8
|
+
if option_opts[:type] == Array
|
9
|
+
h[k] = []
|
10
|
+
else
|
11
|
+
h[k] = option_opts[:default]
|
12
|
+
end
|
13
|
+
}
|
14
|
+
end
|
15
|
+
module_function :defaults
|
16
|
+
|
17
|
+
def default_options
|
18
|
+
{
|
19
|
+
max_violations: 0,
|
20
|
+
exclusions_file: nil,
|
21
|
+
checks: Cane.default_checks
|
22
|
+
}.merge(Cane.default_checks.inject({}) {|a, check|
|
23
|
+
a.merge(defaults(check))
|
24
|
+
})
|
25
|
+
end
|
26
|
+
module_function :default_options
|
27
|
+
end
|
28
|
+
end
|
@@ -1,38 +1,31 @@
|
|
1
1
|
require 'optparse'
|
2
2
|
|
3
|
-
require 'cane/
|
4
|
-
require 'cane/
|
5
|
-
require 'cane/
|
6
|
-
require 'cane/threshold_check'
|
3
|
+
require 'cane/default_checks'
|
4
|
+
require 'cane/cli/options'
|
5
|
+
require 'cane/version'
|
7
6
|
|
8
7
|
module Cane
|
9
8
|
module CLI
|
10
9
|
|
11
10
|
# Provides a specification for the command line interface that drives
|
12
11
|
# documentation, parsing, and default values.
|
13
|
-
class
|
14
|
-
CHECKS = [AbcCheck, StyleCheck, DocCheck, ThresholdCheck]
|
15
|
-
|
16
|
-
def self.defaults(check)
|
17
|
-
x = check.options.each_with_object({}) {|(k, v), h|
|
18
|
-
h[k] = (v[1] || {})[:default]
|
19
|
-
}
|
20
|
-
x
|
21
|
-
end
|
22
|
-
|
23
|
-
OPTIONS = {
|
24
|
-
max_violations: 0,
|
25
|
-
exclusions_file: nil,
|
26
|
-
}.merge(CHECKS.inject({}) {|a, check| a.merge(defaults(check)) })
|
12
|
+
class Parser
|
27
13
|
|
28
14
|
# Exception to indicate that no further processing is required and the
|
29
15
|
# program can exit. This is used to handle --help and --version flags.
|
30
16
|
class OptionsHandled < RuntimeError; end
|
31
17
|
|
32
|
-
def
|
18
|
+
def self.parse(*args)
|
19
|
+
new.parse(*args)
|
20
|
+
end
|
21
|
+
|
22
|
+
def initialize(stdout = $stdout)
|
23
|
+
@stdout = stdout
|
24
|
+
|
33
25
|
add_banner
|
26
|
+
add_user_defined_checks
|
34
27
|
|
35
|
-
|
28
|
+
Cane.default_checks.each do |check|
|
36
29
|
add_check_options(check)
|
37
30
|
end
|
38
31
|
|
@@ -45,7 +38,7 @@ module Cane
|
|
45
38
|
def parse(args, ret = true)
|
46
39
|
parser.parse!(get_default_options + args)
|
47
40
|
|
48
|
-
|
41
|
+
Cane::CLI.default_options.merge(options)
|
49
42
|
rescue OptionParser::InvalidOption
|
50
43
|
args = %w(--help)
|
51
44
|
ret = false
|
@@ -55,8 +48,8 @@ module Cane
|
|
55
48
|
end
|
56
49
|
|
57
50
|
def get_default_options
|
58
|
-
if ::File.exists?('./.cane')
|
59
|
-
::File.
|
51
|
+
if Cane::File.exists?('./.cane')
|
52
|
+
Cane::File.contents('./.cane').split(/\s+/m)
|
60
53
|
else
|
61
54
|
[]
|
62
55
|
end
|
@@ -66,11 +59,26 @@ module Cane
|
|
66
59
|
parser.banner = <<-BANNER
|
67
60
|
Usage: cane [options]
|
68
61
|
|
69
|
-
|
62
|
+
Default options are loaded from a .cane file in the current directory.
|
70
63
|
|
71
64
|
BANNER
|
72
65
|
end
|
73
66
|
|
67
|
+
def add_user_defined_checks
|
68
|
+
description = "Load a Ruby file containing user-defined checks"
|
69
|
+
parser.on("-r", "--require FILE", description) do |f|
|
70
|
+
load(f)
|
71
|
+
end
|
72
|
+
|
73
|
+
description = "Use the given user-defined check"
|
74
|
+
parser.on("-c", "--check CLASS", description) do |c|
|
75
|
+
check = Kernel.const_get(c)
|
76
|
+
options[:checks] << check
|
77
|
+
add_check_options(check)
|
78
|
+
end
|
79
|
+
parser.separator ""
|
80
|
+
end
|
81
|
+
|
74
82
|
def add_check_options(check)
|
75
83
|
check.options.each do |key, data|
|
76
84
|
cli_key = key.to_s.tr('_', '-')
|
@@ -103,14 +111,14 @@ BANNER
|
|
103
111
|
|
104
112
|
def add_version
|
105
113
|
parser.on_tail("-v", "--version", "Show version") do
|
106
|
-
puts Cane::VERSION
|
114
|
+
stdout.puts Cane::VERSION
|
107
115
|
raise OptionsHandled
|
108
116
|
end
|
109
117
|
end
|
110
118
|
|
111
119
|
def add_help
|
112
120
|
parser.on_tail("-h", "--help", "Show this message") do
|
113
|
-
puts parser
|
121
|
+
stdout.puts parser
|
114
122
|
raise OptionsHandled
|
115
123
|
end
|
116
124
|
end
|
@@ -131,12 +139,16 @@ BANNER
|
|
131
139
|
end
|
132
140
|
|
133
141
|
def options
|
134
|
-
@options ||= {
|
142
|
+
@options ||= {
|
143
|
+
checks: Cane.default_checks
|
144
|
+
}
|
135
145
|
end
|
136
146
|
|
137
147
|
def parser
|
138
148
|
@parser ||= OptionParser.new
|
139
149
|
end
|
150
|
+
|
151
|
+
attr_reader :stdout
|
140
152
|
end
|
141
153
|
|
142
154
|
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
require 'cane/abc_check'
|
2
|
+
require 'cane/style_check'
|
3
|
+
require 'cane/doc_check'
|
4
|
+
require 'cane/threshold_check'
|
5
|
+
|
6
|
+
module Cane
|
7
|
+
def default_checks
|
8
|
+
[
|
9
|
+
AbcCheck,
|
10
|
+
StyleCheck,
|
11
|
+
DocCheck,
|
12
|
+
ThresholdCheck
|
13
|
+
]
|
14
|
+
end
|
15
|
+
module_function :default_checks
|
16
|
+
end
|
data/lib/cane/doc_check.rb
CHANGED
@@ -31,7 +31,7 @@ module Cane
|
|
31
31
|
|
32
32
|
def find_violations(file_name)
|
33
33
|
last_line = ""
|
34
|
-
Cane::File.iterator(file_name).
|
34
|
+
Cane::File.iterator(file_name).map.with_index do |line, number|
|
35
35
|
result = if class_definition?(line) && !comment?(last_line)
|
36
36
|
{
|
37
37
|
file: file_name,
|
@@ -1,35 +1,30 @@
|
|
1
1
|
module Cane
|
2
2
|
|
3
3
|
# Provides iteration over lines (from a file), correctly handling encoding.
|
4
|
-
class EncodingAwareIterator
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
end
|
10
|
-
end
|
4
|
+
class EncodingAwareIterator
|
5
|
+
include Enumerable
|
6
|
+
|
7
|
+
def initialize(lines)
|
8
|
+
@lines = lines
|
11
9
|
end
|
12
10
|
|
13
|
-
|
11
|
+
def each(&block)
|
12
|
+
return Enumerator.new(self, :each) unless block
|
14
13
|
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
retried = false
|
20
|
-
begin
|
21
|
-
block.call(line)
|
22
|
-
rescue ArgumentError
|
23
|
-
if retried
|
24
|
-
# I haven't seen input that causes this to occur. Please report it!
|
25
|
-
raise
|
26
|
-
else
|
14
|
+
lines.each do |line|
|
15
|
+
begin
|
16
|
+
line =~ /\s/
|
17
|
+
rescue ArgumentError
|
27
18
|
line.encode!('UTF-8', 'UTF-8', invalid: :replace)
|
28
|
-
retried = true
|
29
|
-
retry
|
30
19
|
end
|
20
|
+
|
21
|
+
block.call(line)
|
31
22
|
end
|
32
23
|
end
|
24
|
+
|
25
|
+
protected
|
26
|
+
|
27
|
+
attr_reader :lines
|
33
28
|
end
|
34
29
|
|
35
30
|
end
|
data/lib/cane/file.rb
CHANGED
data/lib/cane/rake_task.rb
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
require 'rake'
|
2
2
|
require 'rake/tasklib'
|
3
3
|
|
4
|
-
require 'cane/cli/
|
4
|
+
require 'cane/cli/options'
|
5
5
|
|
6
6
|
module Cane
|
7
7
|
# Creates a rake task to run cane with given configuration.
|
@@ -17,22 +17,36 @@ module Cane
|
|
17
17
|
# end
|
18
18
|
class RakeTask < ::Rake::TaskLib
|
19
19
|
attr_accessor :name
|
20
|
-
|
21
|
-
|
22
|
-
|
20
|
+
attr_reader :options
|
21
|
+
|
22
|
+
Cane::CLI.default_options.keys.each do |name|
|
23
|
+
define_method(name) do
|
24
|
+
options.fetch(name)
|
25
|
+
end
|
26
|
+
|
27
|
+
define_method("#{name}=") do |v|
|
28
|
+
options[name] = v
|
29
|
+
end
|
23
30
|
end
|
24
31
|
|
25
32
|
# Add a threshold check. If the file exists and it contains a number,
|
26
33
|
# compare that number with the given value using the operator.
|
27
34
|
def add_threshold(file, operator, value)
|
28
35
|
if operator == :>=
|
29
|
-
@gte << [file, value]
|
36
|
+
@options[:gte] << [file, value]
|
30
37
|
end
|
31
38
|
end
|
32
39
|
|
40
|
+
def use(check, options = {})
|
41
|
+
@options.merge!(options)
|
42
|
+
@options[:checks] = @options[:checks] + [check]
|
43
|
+
end
|
44
|
+
|
33
45
|
def initialize(task_name = nil)
|
34
46
|
self.name = task_name || :cane
|
35
47
|
@gte = []
|
48
|
+
@options = Cane::CLI.default_options
|
49
|
+
|
36
50
|
yield self if block_given?
|
37
51
|
|
38
52
|
unless ::Rake.application.last_comment
|
@@ -41,15 +55,7 @@ module Cane
|
|
41
55
|
|
42
56
|
task name do
|
43
57
|
require 'cane/cli'
|
44
|
-
abort unless Cane.run(
|
45
|
-
end
|
46
|
-
end
|
47
|
-
|
48
|
-
def options
|
49
|
-
OPTIONS.keys.inject({}) do |opts, setting|
|
50
|
-
value = self.send(setting)
|
51
|
-
opts[setting] = value unless value.nil?
|
52
|
-
opts
|
58
|
+
abort unless Cane.run(options)
|
53
59
|
end
|
54
60
|
end
|
55
61
|
end
|
data/lib/cane/style_check.rb
CHANGED
data/lib/cane/version.rb
CHANGED
data/spec/cane_spec.rb
CHANGED
@@ -2,26 +2,14 @@ require 'spec_helper'
|
|
2
2
|
require "stringio"
|
3
3
|
require 'cane/cli'
|
4
4
|
|
5
|
-
|
6
|
-
def capture_stdout &block
|
7
|
-
real_stdout, $stdout = $stdout, StringIO.new
|
8
|
-
yield
|
9
|
-
$stdout.string
|
10
|
-
ensure
|
11
|
-
$stdout = real_stdout
|
12
|
-
end
|
5
|
+
require 'cane/rake_task'
|
13
6
|
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
result = Cane::CLI.run(['--no-abc'] + cli_args.split(' '))
|
18
|
-
end
|
7
|
+
# Acceptance tests
|
8
|
+
describe 'The cane application' do
|
9
|
+
let(:class_name) { "C#{rand(10 ** 10)}" }
|
19
10
|
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
it 'fails if ABC metric does not meet requirements' do
|
24
|
-
file_name = make_file(<<-RUBY)
|
11
|
+
it 'returns a non-zero exit code and a details of checks that failed' do
|
12
|
+
fn = make_file(<<-RUBY + " ")
|
25
13
|
class Harness
|
26
14
|
def complex_method(a)
|
27
15
|
if a < 2
|
@@ -33,126 +21,37 @@ describe 'Cane' do
|
|
33
21
|
end
|
34
22
|
RUBY
|
35
23
|
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
output, exitstatus = run("--style-glob #{file_name}")
|
45
|
-
output.should include("Lines violated style requirements")
|
46
|
-
exitstatus.should == 1
|
47
|
-
end
|
48
|
-
|
49
|
-
it 'allows measure to be configured' do
|
50
|
-
file_name = make_file("toolong")
|
51
|
-
|
52
|
-
output, exitstatus = run("--style-glob #{file_name} --style-measure 3")
|
53
|
-
exitstatus.should == 1
|
54
|
-
output.should include("Lines violated style requirements")
|
55
|
-
end
|
56
|
-
|
57
|
-
it 'does not include trailing new lines in the character count' do
|
58
|
-
file_name = make_file('#' * 80 + "\n" + '#' * 80)
|
59
|
-
|
60
|
-
output, exitstatus = run("--style-glob #{file_name} --style-measure 80")
|
61
|
-
exitstatus.should == 0
|
62
|
-
output.should == ""
|
63
|
-
end
|
64
|
-
|
65
|
-
it 'allows upper bound of failed checks' do
|
66
|
-
file_name = make_file("whitespace ")
|
67
|
-
|
68
|
-
output, exitstatus = run("--style-glob #{file_name} --max-violations 1")
|
69
|
-
exitstatus.should == 0
|
70
|
-
output.should include("Lines violated style requirements")
|
71
|
-
end
|
72
|
-
|
73
|
-
it 'allows checking of a value in a file' do
|
74
|
-
file_name = make_file("89")
|
75
|
-
|
76
|
-
output, exitstatus = run("--gte #{file_name},90")
|
77
|
-
output.should include("Quality threshold crossed")
|
78
|
-
exitstatus.should == 1
|
79
|
-
end
|
80
|
-
|
81
|
-
it 'allows checking of class documentation' do
|
82
|
-
file_name = make_file("class NoDoc")
|
83
|
-
|
84
|
-
output, exitstatus = run("--doc-glob #{file_name}")
|
85
|
-
exitstatus.should == 1
|
86
|
-
output.should include("Classes are not documented")
|
87
|
-
end
|
88
|
-
|
89
|
-
context 'with a .cane file' do
|
90
|
-
before(:each) do
|
91
|
-
file_name = make_file("class NoDoc")
|
92
|
-
make_dot_cane("--doc-glob #{file_name}")
|
93
|
-
end
|
94
|
-
|
95
|
-
after(:each) do
|
96
|
-
unmake_dot_cane
|
97
|
-
end
|
98
|
-
|
99
|
-
it 'loads options from a .cane file' do
|
100
|
-
output, exitstatus = run('')
|
101
|
-
|
102
|
-
exitstatus.should == 1
|
103
|
-
output.should include("Classes are not documented")
|
104
|
-
end
|
105
|
-
end
|
106
|
-
|
107
|
-
it 'displays a help message' do
|
108
|
-
output, exitstatus = run("--help")
|
109
|
-
|
110
|
-
exitstatus.should == 0
|
111
|
-
output.should include("Usage:")
|
112
|
-
end
|
113
|
-
|
114
|
-
it 'displays version' do
|
115
|
-
output, exitstatus = run("--version")
|
116
|
-
|
117
|
-
exitstatus.should == 0
|
118
|
-
output.should include(Cane::VERSION)
|
119
|
-
end
|
120
|
-
|
121
|
-
it 'uses the last of conflicting arguments' do
|
122
|
-
file_name = make_file("class NoDoc")
|
123
|
-
|
124
|
-
run("--doc-glob #{file_name} --no-doc").should ==
|
125
|
-
run("--no-doc")
|
126
|
-
|
127
|
-
run("--no-doc --doc-glob #{file_name}").should ==
|
128
|
-
run("--doc-glob #{file_name}")
|
129
|
-
end
|
24
|
+
check_file = make_file <<-RUBY
|
25
|
+
class #{class_name} < Struct.new(:opts)
|
26
|
+
def self.options
|
27
|
+
{
|
28
|
+
unhappy_file: ["File to check", default: [nil]]
|
29
|
+
}
|
30
|
+
end
|
130
31
|
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
if a < 2
|
138
|
-
return "low"
|
139
|
-
else
|
140
|
-
return "high"
|
141
|
-
end
|
32
|
+
def violations
|
33
|
+
[
|
34
|
+
description: "Files are unhappy",
|
35
|
+
file: opts.fetch(:unhappy_file),
|
36
|
+
label: ":("
|
37
|
+
]
|
142
38
|
end
|
143
39
|
end
|
144
40
|
RUBY
|
145
41
|
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
|
151
|
-
|
152
|
-
|
153
|
-
|
154
|
-
|
155
|
-
|
42
|
+
output, exitstatus = run %(
|
43
|
+
--style-glob #{fn}
|
44
|
+
--doc-glob #{fn}
|
45
|
+
--abc-glob #{fn}
|
46
|
+
--abc-max 1
|
47
|
+
-r #{check_file}
|
48
|
+
--check #{class_name}
|
49
|
+
--unhappy-file #{fn}
|
50
|
+
)
|
51
|
+
output.should include("Lines violated style requirements")
|
52
|
+
output.should include("Classes are not documented")
|
53
|
+
output.should include("Methods exceeded maximum allowed ABC complexity")
|
54
|
+
exitstatus.should == 1
|
156
55
|
end
|
157
56
|
|
158
57
|
it 'handles invalid unicode input' do
|
@@ -163,10 +62,20 @@ describe 'Cane' do
|
|
163
62
|
exitstatus.should == 0
|
164
63
|
end
|
165
64
|
|
166
|
-
|
167
|
-
|
65
|
+
after do
|
66
|
+
if Object.const_defined?(class_name)
|
67
|
+
Object.send(:remove_const, class_name)
|
68
|
+
end
|
69
|
+
end
|
168
70
|
|
169
|
-
|
170
|
-
|
71
|
+
def run(cli_args)
|
72
|
+
result = nil
|
73
|
+
output = capture_stdout do
|
74
|
+
result = Cane::CLI.run(
|
75
|
+
%w(--no-abc --no-style --no-doc) + cli_args.split(/\s+/m)
|
76
|
+
)
|
77
|
+
end
|
78
|
+
|
79
|
+
[output, result ? 0 : 1]
|
171
80
|
end
|
172
81
|
end
|
data/spec/cli_spec.rb
ADDED
@@ -0,0 +1,24 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
require 'cane/cli'
|
4
|
+
|
5
|
+
describe Cane::CLI do
|
6
|
+
describe '.run' do
|
7
|
+
|
8
|
+
let!(:parser) { fire_replaced_class_double("Cane::CLI::Parser") }
|
9
|
+
let!(:cane) { fire_replaced_class_double("Cane") }
|
10
|
+
|
11
|
+
it 'runs Cane with the given arguments' do
|
12
|
+
parser.should_receive(:parse).with("--args").and_return(args: true)
|
13
|
+
cane.should_receive(:run).with(args: true).and_return("tracer")
|
14
|
+
|
15
|
+
described_class.run("--args").should == "tracer"
|
16
|
+
end
|
17
|
+
|
18
|
+
it 'does not run Cane if parser was able to handle input' do
|
19
|
+
parser.should_receive(:parse).with("--args").and_return("tracer")
|
20
|
+
|
21
|
+
described_class.run("--args").should == "tracer"
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
@@ -8,7 +8,8 @@ require 'cane/encoding_aware_iterator'
|
|
8
8
|
describe Cane::EncodingAwareIterator do
|
9
9
|
it 'handles non-UTF8 input' do
|
10
10
|
lines = ["\xc3\x28"]
|
11
|
-
result = described_class.new(lines).
|
11
|
+
result = described_class.new(lines).map.with_index do |line, number|
|
12
|
+
line.should be_kind_of(String)
|
12
13
|
[line =~ /\s/, number]
|
13
14
|
end
|
14
15
|
result.should == [[nil, 0]]
|
@@ -16,9 +17,17 @@ describe Cane::EncodingAwareIterator do
|
|
16
17
|
|
17
18
|
it 'does not enter an infinite loop on persistently bad input' do
|
18
19
|
->{
|
19
|
-
described_class.new([""]).
|
20
|
+
described_class.new([""]).map.with_index do |line, number|
|
20
21
|
"\xc3\x28" =~ /\s/
|
21
22
|
end
|
22
23
|
}.should raise_error(ArgumentError)
|
23
24
|
end
|
25
|
+
|
26
|
+
it 'allows each with no block' do
|
27
|
+
called_with_line = nil
|
28
|
+
described_class.new([""]).each.with_index do |line, number|
|
29
|
+
called_with_line = line
|
30
|
+
end
|
31
|
+
called_with_line.should == ""
|
32
|
+
end
|
24
33
|
end
|
data/spec/parser_spec.rb
ADDED
@@ -0,0 +1,89 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require "stringio"
|
3
|
+
require 'cane/cli/parser'
|
4
|
+
|
5
|
+
describe Cane::CLI::Parser do
|
6
|
+
def run(cli_args)
|
7
|
+
result = nil
|
8
|
+
output = StringIO.new("")
|
9
|
+
result = Cane::CLI::Parser.new(output).parse(cli_args.split(/\s+/m))
|
10
|
+
|
11
|
+
[output.string, result]
|
12
|
+
end
|
13
|
+
|
14
|
+
it 'allows style options to be configured' do
|
15
|
+
output, result = run("--style-glob myfile --style-measure 3")
|
16
|
+
result[:style_glob].should == 'myfile'
|
17
|
+
result[:style_measure].should == 3
|
18
|
+
end
|
19
|
+
|
20
|
+
it 'allows checking of a value in a file' do
|
21
|
+
output, result = run("--gte myfile,90")
|
22
|
+
result[:gte].should == [['myfile', '90']]
|
23
|
+
end
|
24
|
+
|
25
|
+
it 'allows upper bound of failed checks' do
|
26
|
+
output, result = run("--max-violations 1")
|
27
|
+
result[:max_violations].should == 1
|
28
|
+
end
|
29
|
+
|
30
|
+
it 'displays a help message' do
|
31
|
+
output, result = run("--help")
|
32
|
+
|
33
|
+
result.should be
|
34
|
+
output.should include("Usage:")
|
35
|
+
end
|
36
|
+
|
37
|
+
it 'handles invalid options by showing help' do
|
38
|
+
output, result = run("--bogus")
|
39
|
+
|
40
|
+
output.should include("Usage:")
|
41
|
+
result.should_not be
|
42
|
+
end
|
43
|
+
|
44
|
+
it 'displays version' do
|
45
|
+
output, result = run("--version")
|
46
|
+
|
47
|
+
result.should be
|
48
|
+
output.should include(Cane::VERSION)
|
49
|
+
end
|
50
|
+
|
51
|
+
it 'supports exclusions' do
|
52
|
+
options = [
|
53
|
+
"--abc-exclude", "Harness#complex_method",
|
54
|
+
"--style-exclude", 'myfile'
|
55
|
+
].join(' ')
|
56
|
+
|
57
|
+
_, result = run(options)
|
58
|
+
result[:abc_exclude].should == [['Harness#complex_method']]
|
59
|
+
result[:style_exclude].should == [['myfile']]
|
60
|
+
end
|
61
|
+
|
62
|
+
describe 'argument ordering' do
|
63
|
+
it 'gives precedence to the last argument #1' do
|
64
|
+
_, result = run("--doc-glob myfile --no-doc")
|
65
|
+
result[:no_doc].should be
|
66
|
+
|
67
|
+
_, result = run("--no-doc --doc-glob myfile")
|
68
|
+
result[:no_doc].should_not be
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
it 'loads default options from .cane file' do
|
73
|
+
defaults = <<-EOS
|
74
|
+
--no-doc
|
75
|
+
--abc-glob myfile
|
76
|
+
--style-glob myfile
|
77
|
+
EOS
|
78
|
+
file = fire_replaced_class_double("Cane::File")
|
79
|
+
stub_const("Cane::File", file)
|
80
|
+
file.should_receive(:exists?).with('./.cane').and_return(true)
|
81
|
+
file.should_receive(:contents).with('./.cane').and_return(defaults)
|
82
|
+
|
83
|
+
_, result = run("--style-glob myotherfile")
|
84
|
+
|
85
|
+
result[:no_doc].should be
|
86
|
+
result[:abc_glob].should == 'myfile'
|
87
|
+
result[:style_glob].should == 'myotherfile'
|
88
|
+
end
|
89
|
+
end
|
data/spec/rake_task_spec.rb
CHANGED
@@ -3,11 +3,34 @@ require 'spec_helper'
|
|
3
3
|
require 'cane/rake_task'
|
4
4
|
|
5
5
|
describe Cane::RakeTask do
|
6
|
-
it '
|
7
|
-
|
8
|
-
|
6
|
+
it 'enables cane to be configured an run via rake' do
|
7
|
+
fn = make_file("90")
|
8
|
+
my_check = Class.new(Struct.new(:opts)) do
|
9
|
+
def violations
|
10
|
+
[description: 'test', label: opts.fetch(:some_opt)]
|
11
|
+
end
|
9
12
|
end
|
10
13
|
|
11
|
-
task.
|
14
|
+
task = Cane::RakeTask.new(:quality) do |cane|
|
15
|
+
cane.no_abc = true
|
16
|
+
cane.no_doc = true
|
17
|
+
cane.no_style = true
|
18
|
+
cane.add_threshold fn, :>=, 99
|
19
|
+
cane.use my_check, some_opt: "theopt"
|
20
|
+
end
|
21
|
+
|
22
|
+
task.no_abc.should == true
|
23
|
+
|
24
|
+
task.should_receive(:abort)
|
25
|
+
out = capture_stdout do
|
26
|
+
Rake::Task['quality'].invoke
|
27
|
+
end
|
28
|
+
|
29
|
+
out.should include("Quality threshold crossed")
|
30
|
+
out.should include("theopt")
|
31
|
+
end
|
32
|
+
|
33
|
+
after do
|
34
|
+
Rake::Task.clear
|
12
35
|
end
|
13
36
|
end
|
data/spec/runner_spec.rb
ADDED
@@ -0,0 +1,12 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
require 'cane'
|
4
|
+
|
5
|
+
describe Cane::Runner do
|
6
|
+
describe '#run' do
|
7
|
+
it 'returns true iff fewer violations than max allowed' do
|
8
|
+
described_class.new(checks: [], max_violations: 0).run.should be
|
9
|
+
described_class.new(checks: [], max_violations: -1).run.should_not be
|
10
|
+
end
|
11
|
+
end
|
12
|
+
end
|
data/spec/spec_helper.rb
CHANGED
@@ -1,4 +1,20 @@
|
|
1
|
+
require 'rspec/fire'
|
1
2
|
require 'tempfile'
|
3
|
+
require 'stringio'
|
4
|
+
require 'rake'
|
5
|
+
require 'rake/tasklib'
|
6
|
+
|
7
|
+
RSpec.configure do |config|
|
8
|
+
config.include(RSpec::Fire)
|
9
|
+
|
10
|
+
def capture_stdout &block
|
11
|
+
real_stdout, $stdout = $stdout, StringIO.new
|
12
|
+
yield
|
13
|
+
$stdout.string
|
14
|
+
ensure
|
15
|
+
$stdout = real_stdout
|
16
|
+
end
|
17
|
+
end
|
2
18
|
|
3
19
|
# Keep a reference to all tempfiles so they are not garbage collected until the
|
4
20
|
# process exits.
|
@@ -12,16 +28,6 @@ def make_file(content)
|
|
12
28
|
tempfile.path
|
13
29
|
end
|
14
30
|
|
15
|
-
def make_dot_cane(content)
|
16
|
-
File.open('./.cane', 'w') do |f|
|
17
|
-
f.puts content
|
18
|
-
end
|
19
|
-
end
|
20
|
-
|
21
|
-
def unmake_dot_cane
|
22
|
-
FileUtils.rm('./.cane')
|
23
|
-
end
|
24
|
-
|
25
31
|
require 'simplecov'
|
26
32
|
|
27
33
|
class SimpleCov::Formatter::QualityFormatter
|
data/spec/style_check_spec.rb
CHANGED
@@ -18,8 +18,8 @@ describe Cane::StyleCheck do
|
|
18
18
|
it 'creates a StyleViolation for each method above the threshold' do
|
19
19
|
file_name = make_file(ruby_with_style_issue)
|
20
20
|
|
21
|
-
violations = check(file_name, style_measure:
|
22
|
-
violations.length.should ==
|
21
|
+
violations = check(file_name, style_measure: 8).violations
|
22
|
+
violations.length.should == 3
|
23
23
|
end
|
24
24
|
|
25
25
|
it 'skips declared exclusions' do
|
@@ -32,4 +32,16 @@ describe Cane::StyleCheck do
|
|
32
32
|
|
33
33
|
violations.length.should == 0
|
34
34
|
end
|
35
|
+
|
36
|
+
it 'does not include trailing new lines in the character count' do
|
37
|
+
file_name = make_file('#' * 80 + "\n" + '#' * 80)
|
38
|
+
|
39
|
+
violations = check(file_name,
|
40
|
+
style_measure: 80,
|
41
|
+
style_exclude: [file_name]
|
42
|
+
).violations
|
43
|
+
|
44
|
+
violations.length.should == 0
|
45
|
+
end
|
46
|
+
|
35
47
|
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: cane
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 2.
|
4
|
+
version: 2.1.0
|
5
5
|
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -9,11 +9,11 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2012-08-
|
12
|
+
date: 2012-08-26 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: rspec
|
16
|
-
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirement: &2152567380 !ruby/object:Gem::Requirement
|
17
17
|
none: false
|
18
18
|
requirements:
|
19
19
|
- - ~>
|
@@ -21,15 +21,10 @@ dependencies:
|
|
21
21
|
version: '2.0'
|
22
22
|
type: :development
|
23
23
|
prerelease: false
|
24
|
-
version_requirements:
|
25
|
-
none: false
|
26
|
-
requirements:
|
27
|
-
- - ~>
|
28
|
-
- !ruby/object:Gem::Version
|
29
|
-
version: '2.0'
|
24
|
+
version_requirements: *2152567380
|
30
25
|
- !ruby/object:Gem::Dependency
|
31
26
|
name: rake
|
32
|
-
requirement: !ruby/object:Gem::Requirement
|
27
|
+
requirement: &2152566600 !ruby/object:Gem::Requirement
|
33
28
|
none: false
|
34
29
|
requirements:
|
35
30
|
- - ! '>='
|
@@ -37,15 +32,10 @@ dependencies:
|
|
37
32
|
version: '0'
|
38
33
|
type: :development
|
39
34
|
prerelease: false
|
40
|
-
version_requirements:
|
41
|
-
none: false
|
42
|
-
requirements:
|
43
|
-
- - ! '>='
|
44
|
-
- !ruby/object:Gem::Version
|
45
|
-
version: '0'
|
35
|
+
version_requirements: *2152566600
|
46
36
|
- !ruby/object:Gem::Dependency
|
47
37
|
name: simplecov
|
48
|
-
requirement: !ruby/object:Gem::Requirement
|
38
|
+
requirement: &2152565760 !ruby/object:Gem::Requirement
|
49
39
|
none: false
|
50
40
|
requirements:
|
51
41
|
- - ! '>='
|
@@ -53,12 +43,18 @@ dependencies:
|
|
53
43
|
version: '0'
|
54
44
|
type: :development
|
55
45
|
prerelease: false
|
56
|
-
version_requirements:
|
46
|
+
version_requirements: *2152565760
|
47
|
+
- !ruby/object:Gem::Dependency
|
48
|
+
name: rspec-fire
|
49
|
+
requirement: &2152564960 !ruby/object:Gem::Requirement
|
57
50
|
none: false
|
58
51
|
requirements:
|
59
52
|
- - ! '>='
|
60
53
|
- !ruby/object:Gem::Version
|
61
54
|
version: '0'
|
55
|
+
type: :development
|
56
|
+
prerelease: false
|
57
|
+
version_requirements: *2152564960
|
62
58
|
description: Fails your build if code quality thresholds are not met
|
63
59
|
email:
|
64
60
|
- xavier@squareup.com
|
@@ -69,16 +65,21 @@ extra_rdoc_files: []
|
|
69
65
|
files:
|
70
66
|
- spec/abc_check_spec.rb
|
71
67
|
- spec/cane_spec.rb
|
68
|
+
- spec/cli_spec.rb
|
72
69
|
- spec/doc_check_spec.rb
|
73
70
|
- spec/encoding_aware_iterator_spec.rb
|
71
|
+
- spec/parser_spec.rb
|
74
72
|
- spec/rake_task_spec.rb
|
73
|
+
- spec/runner_spec.rb
|
75
74
|
- spec/spec_helper.rb
|
76
75
|
- spec/style_check_spec.rb
|
77
76
|
- spec/threshold_check_spec.rb
|
78
77
|
- spec/violation_formatter_spec.rb
|
79
78
|
- lib/cane/abc_check.rb
|
80
|
-
- lib/cane/cli/
|
79
|
+
- lib/cane/cli/options.rb
|
80
|
+
- lib/cane/cli/parser.rb
|
81
81
|
- lib/cane/cli.rb
|
82
|
+
- lib/cane/default_checks.rb
|
82
83
|
- lib/cane/doc_check.rb
|
83
84
|
- lib/cane/encoding_aware_iterator.rb
|
84
85
|
- lib/cane/file.rb
|
@@ -113,7 +114,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
113
114
|
version: '0'
|
114
115
|
requirements: []
|
115
116
|
rubyforge_project:
|
116
|
-
rubygems_version: 1.8.
|
117
|
+
rubygems_version: 1.8.6
|
117
118
|
signing_key:
|
118
119
|
specification_version: 3
|
119
120
|
summary: Fails your build if code quality thresholds are not met. Provides complexity
|
@@ -121,9 +122,12 @@ summary: Fails your build if code quality thresholds are not met. Provides compl
|
|
121
122
|
test_files:
|
122
123
|
- spec/abc_check_spec.rb
|
123
124
|
- spec/cane_spec.rb
|
125
|
+
- spec/cli_spec.rb
|
124
126
|
- spec/doc_check_spec.rb
|
125
127
|
- spec/encoding_aware_iterator_spec.rb
|
128
|
+
- spec/parser_spec.rb
|
126
129
|
- spec/rake_task_spec.rb
|
130
|
+
- spec/runner_spec.rb
|
127
131
|
- spec/spec_helper.rb
|
128
132
|
- spec/style_check_spec.rb
|
129
133
|
- spec/threshold_check_spec.rb
|