ruby-appraiser 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +17 -0
- data/Gemfile +5 -0
- data/LICENSE-APACHE.md +13 -0
- data/LICENSE-MIT.md +22 -0
- data/LICENSE.md +4 -0
- data/README.md +77 -0
- data/Rakefile +2 -0
- data/bin/ruby-appraiser +17 -0
- data/lib/ruby-appraiser/adapter/line-length.rb +19 -0
- data/lib/ruby-appraiser/adapter.rb +88 -0
- data/lib/ruby-appraiser/appraisal.rb +126 -0
- data/lib/ruby-appraiser/cli.rb +98 -0
- data/lib/ruby-appraiser/defect.rb +33 -0
- data/lib/ruby-appraiser/git.rb +41 -0
- data/lib/ruby-appraiser/version.rb +5 -0
- data/lib/ruby-appraiser.rb +64 -0
- data/ruby-appraiser.gemspec +19 -0
- metadata +64 -0
data/.gitignore
ADDED
data/Gemfile
ADDED
data/LICENSE-APACHE.md
ADDED
@@ -0,0 +1,13 @@
|
|
1
|
+
Copyright 2013 Simply Measured
|
2
|
+
|
3
|
+
Licensed under the Apache License, Version 2.0 (the "License");
|
4
|
+
you may not use this file except in compliance with the License.
|
5
|
+
You may obtain a copy of the License at
|
6
|
+
|
7
|
+
http://www.apache.org/licenses/LICENSE-2.0
|
8
|
+
|
9
|
+
Unless required by applicable law or agreed to in writing, software
|
10
|
+
distributed under the License is distributed on an "AS IS" BASIS,
|
11
|
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
12
|
+
See the License for the specific language governing permissions and
|
13
|
+
limitations under the License.
|
data/LICENSE-MIT.md
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2013 Simply Measured
|
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/LICENSE.md
ADDED
data/README.md
ADDED
@@ -0,0 +1,77 @@
|
|
1
|
+
RubyAppraiser
|
2
|
+
=============
|
3
|
+
|
4
|
+
So you have a big project and you want to improve the code quality? Sweet. Too
|
5
|
+
bad you'll get a million errors when you run [rubocop][], [reek][], or
|
6
|
+
[flog][], so it'll annoy you with information overload until you get fed up &
|
7
|
+
turn it off.
|
8
|
+
|
9
|
+
Enter: RubyAppraiser, a generic interface for attaching code-quality tools
|
10
|
+
that limits their output to the lines you're changing, which allows you to use
|
11
|
+
these tools to gradually heal projects. Add a pre-commit hook that rejects
|
12
|
+
defective contributions, level up to require entire touched files to be fixed,
|
13
|
+
or run several code-quality tools in a single command.
|
14
|
+
|
15
|
+
The filters currently provided are:
|
16
|
+
|
17
|
+
- all - (default) show all defects
|
18
|
+
- authored - all uncommitted defects
|
19
|
+
- staged - all staged defects
|
20
|
+
- touched - all defects in files that have been touched
|
21
|
+
|
22
|
+
Usage:
|
23
|
+
------
|
24
|
+
|
25
|
+
1. Include one or more adapters in your `Gemfile` or as development
|
26
|
+
dependencies of your gem. They'll make sure their dependencies (including
|
27
|
+
`ruby-appraiser` itself) are taken care of.
|
28
|
+
|
29
|
+
```ruby
|
30
|
+
gem 'ruby-appraiser-rubocop'
|
31
|
+
gem 'ruby-appraiser-reek'
|
32
|
+
```
|
33
|
+
|
34
|
+
2. Execute the appraiser:
|
35
|
+
|
36
|
+
```sh
|
37
|
+
bundle exec ruby-appraiser --mode=authored reek rubocop
|
38
|
+
```
|
39
|
+
|
40
|
+
The script will exit 0 IFF there are no matching defects from any of your
|
41
|
+
coverage tools. The tools themselves will respect any project-wide settings or
|
42
|
+
config files.
|
43
|
+
|
44
|
+
```
|
45
|
+
$ bundle exec ruby-appraiser --help
|
46
|
+
Usage: ruby-appraiser [inspector...] [options]
|
47
|
+
-v, --[no-]verbose Run verbosely
|
48
|
+
--list List available adapters
|
49
|
+
--silent Silence output
|
50
|
+
--mode=MODE Set the mode. [staged,authored,touched,all]
|
51
|
+
--git-hook Output a git hook with current comand to STDOUT
|
52
|
+
--all Run all available adapters.
|
53
|
+
```
|
54
|
+
|
55
|
+
Contributing:
|
56
|
+
-------------
|
57
|
+
|
58
|
+
1. Write an adapter! Take a look at the existing adapters for help.
|
59
|
+
|
60
|
+
```ruby
|
61
|
+
class Foo < RubyAppraiser::Adapter
|
62
|
+
def appraise
|
63
|
+
# ...
|
64
|
+
add_defect( file, line, description )
|
65
|
+
# ...
|
66
|
+
end
|
67
|
+
end
|
68
|
+
```
|
69
|
+
|
70
|
+
License
|
71
|
+
-------
|
72
|
+
See [LICENSE][]
|
73
|
+
|
74
|
+
[LICENSE]: LICENSE.md
|
75
|
+
[rubocop]: https://github.com/bbatslov/rubocop
|
76
|
+
[reek]: https://github.com/troessner/reek
|
77
|
+
[flog]: https://github.com/seattlerb/flog
|
data/Rakefile
ADDED
data/bin/ruby-appraiser
ADDED
@@ -0,0 +1,17 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# encoding: utf-8
|
3
|
+
|
4
|
+
$LOAD_PATH.unshift(File.dirname(File.realpath(__FILE__)) + '/../lib')
|
5
|
+
|
6
|
+
require 'ruby-appraiser'
|
7
|
+
require 'benchmark'
|
8
|
+
|
9
|
+
cli = RubyAppraiser::CLI.new
|
10
|
+
success = nil
|
11
|
+
|
12
|
+
time = Benchmark::realtime do
|
13
|
+
success = cli.run
|
14
|
+
end
|
15
|
+
|
16
|
+
puts "Finished in #{time} seconds" if cli.options[:verbose]
|
17
|
+
exit(success ? 0 : 1)
|
@@ -0,0 +1,19 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
require 'ruby-appraiser'
|
4
|
+
|
5
|
+
class RubyAppraiser::Adapter::LineLength < RubyAppraiser::Adapter
|
6
|
+
def appraise
|
7
|
+
source_files.each do |source_file|
|
8
|
+
File.open(source_file) do |source|
|
9
|
+
source.lines.each_with_index do |line, number|
|
10
|
+
line_length = line.chomp.length
|
11
|
+
if line_length > 80
|
12
|
+
add_defect(source_file, number,
|
13
|
+
"Line too long [#{line_length}/80]")
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,88 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
require 'set'
|
4
|
+
require 'forwardable'
|
5
|
+
|
6
|
+
class RubyAppraiser::Adapter
|
7
|
+
class << self
|
8
|
+
def inherited(base)
|
9
|
+
registry << base
|
10
|
+
end
|
11
|
+
|
12
|
+
def registry
|
13
|
+
@registry ||= Set.new
|
14
|
+
end
|
15
|
+
|
16
|
+
def adapter_type(type = nil)
|
17
|
+
@adapter_type = type.to_s unless type.nil?
|
18
|
+
|
19
|
+
@adapter_type or
|
20
|
+
self.name.split('::').last.
|
21
|
+
sub(/Adapter$/, '').
|
22
|
+
gsub(/[A-Z]+/, '-\0').
|
23
|
+
sub(/^-/, '').
|
24
|
+
downcase
|
25
|
+
end
|
26
|
+
|
27
|
+
def find(query)
|
28
|
+
registry.detect do |adapter|
|
29
|
+
adapter.adapter_type == query
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
def get(query)
|
34
|
+
find(query) or
|
35
|
+
attempt_require_adapter(query, "ruby-appraiser/adapter/#{query}") or
|
36
|
+
attempt_require_adapter(query, "ruby-appraiser/#{query}") or
|
37
|
+
attempt_require_adapter(query, "ruby-appraiser-#{query}")
|
38
|
+
end
|
39
|
+
|
40
|
+
def all
|
41
|
+
# load relevant gems
|
42
|
+
scanner_pattern = /^(ruby-appraiser-([a-zA-Z0-9_\-]+))/
|
43
|
+
(`gem list --local`).scan(scanner_pattern) do |gem_name, adapter_type|
|
44
|
+
attempt_require_adapter(adapter_type, gem_name)
|
45
|
+
end
|
46
|
+
|
47
|
+
# look in the adapter directory
|
48
|
+
Dir::glob(File::expand_path('../adapter/*.rb', __FILE__)) do |filepath|
|
49
|
+
require filepath
|
50
|
+
end
|
51
|
+
|
52
|
+
# return the registry
|
53
|
+
registry
|
54
|
+
end
|
55
|
+
|
56
|
+
def attempt_require_adapter(name, path)
|
57
|
+
require path and find(name)
|
58
|
+
rescue LoadError
|
59
|
+
false
|
60
|
+
end
|
61
|
+
|
62
|
+
def find!(query)
|
63
|
+
find(query) or raise ArgumentError, "Adapter '#{query}' not found."
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
def initialize(appraisal, options)
|
68
|
+
@appraisal = appraisal
|
69
|
+
@options = options.dup
|
70
|
+
end
|
71
|
+
|
72
|
+
def appraise
|
73
|
+
raise NotImplementedError,
|
74
|
+
"#{self.class.name} does not implement appraise."
|
75
|
+
end
|
76
|
+
|
77
|
+
extend Forwardable
|
78
|
+
|
79
|
+
def_delegators :@appraisal, :source_files,
|
80
|
+
:project_root,
|
81
|
+
:relative_path,
|
82
|
+
:add_defect,
|
83
|
+
:authored_lines,
|
84
|
+
:touched_files,
|
85
|
+
:relevant_files,
|
86
|
+
:relevant_lines
|
87
|
+
end
|
88
|
+
|
@@ -0,0 +1,126 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
class RubyAppraiser
|
4
|
+
class Appraisal
|
5
|
+
def initialize(options = {})
|
6
|
+
@options = options.dup.freeze
|
7
|
+
end
|
8
|
+
|
9
|
+
def mode
|
10
|
+
@options.fetch(:mode) { 'all' }
|
11
|
+
end
|
12
|
+
|
13
|
+
def options
|
14
|
+
@options.dup
|
15
|
+
end
|
16
|
+
|
17
|
+
def run!
|
18
|
+
appraisers.each do |appraiser|
|
19
|
+
appraiser.appraise
|
20
|
+
end unless relevant_files.empty?
|
21
|
+
|
22
|
+
@has_run = true
|
23
|
+
end
|
24
|
+
|
25
|
+
def success?
|
26
|
+
run! unless @has_run
|
27
|
+
|
28
|
+
defects.empty?
|
29
|
+
end
|
30
|
+
|
31
|
+
def defects
|
32
|
+
@defects ||= Set.new
|
33
|
+
end
|
34
|
+
|
35
|
+
def adapters
|
36
|
+
@adapters ||= Set.new
|
37
|
+
end
|
38
|
+
|
39
|
+
def appraisers
|
40
|
+
adapters.map do |adapter|
|
41
|
+
adapter.new(self, options)
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
def add_adapter(name)
|
46
|
+
Adapter::get(name).tap do |adapter|
|
47
|
+
adapter and self.adapters << adapter
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
def source_files
|
52
|
+
Dir::glob(File::expand_path('**/*', project_root)).select do |filepath|
|
53
|
+
File::file? filepath and RubyAppraiser::rubytype? filepath
|
54
|
+
end.map { |path| relative_path path }
|
55
|
+
end
|
56
|
+
|
57
|
+
def add_defect(defect, *args)
|
58
|
+
unless defect.kind_of? Defect
|
59
|
+
defect = Defect.new(defect, *args)
|
60
|
+
end
|
61
|
+
defects << defect if match?(defect.location)
|
62
|
+
end
|
63
|
+
|
64
|
+
def to_s
|
65
|
+
defects.to_a.sort.map(&:to_s).join($/)
|
66
|
+
end
|
67
|
+
|
68
|
+
def summary
|
69
|
+
"#{defects.count} defects detected."
|
70
|
+
end
|
71
|
+
|
72
|
+
def project_root
|
73
|
+
@project_root ||= (`git rev-parse --show-toplevel`).chomp
|
74
|
+
end
|
75
|
+
|
76
|
+
def relative_path(path)
|
77
|
+
full_path = File::expand_path(path, project_root)
|
78
|
+
full_path[(project_root.length + 1)..-1]
|
79
|
+
end
|
80
|
+
|
81
|
+
protected
|
82
|
+
|
83
|
+
def match?(location)
|
84
|
+
file, line = *location
|
85
|
+
relevant_lines[file].include? line
|
86
|
+
end
|
87
|
+
|
88
|
+
def relevant_files
|
89
|
+
relevant_lines.keys
|
90
|
+
end
|
91
|
+
|
92
|
+
def relevant_lines
|
93
|
+
case mode
|
94
|
+
when 'staged' then staged_authored_lines
|
95
|
+
when 'authored' then authored_lines
|
96
|
+
when 'touched' then all_lines_in touched_files
|
97
|
+
when 'all' then all_lines_in source_files
|
98
|
+
else raise ArgumentError, "Unsupported mode #{mode}."
|
99
|
+
end
|
100
|
+
end
|
101
|
+
|
102
|
+
# return a hash.
|
103
|
+
# key is a filename
|
104
|
+
# value is a truebot
|
105
|
+
def all_lines_in(files)
|
106
|
+
infinite_set = (0 .. (1.0 / 0))
|
107
|
+
files.reduce(Hash.new { [] }) do |memo, file|
|
108
|
+
memo.merge!(file => infinite_set)
|
109
|
+
end
|
110
|
+
end
|
111
|
+
|
112
|
+
def touched_files
|
113
|
+
@touched_files ||= authored_lines.keys
|
114
|
+
end
|
115
|
+
|
116
|
+
def authored_lines
|
117
|
+
@authored_lines ||=
|
118
|
+
RubyAppraiser::Git::authored_lines
|
119
|
+
end
|
120
|
+
|
121
|
+
def staged_authored_lines
|
122
|
+
@staged_authored_lines ||=
|
123
|
+
RubyAppraiser::Git::authored_lines(staged: true)
|
124
|
+
end
|
125
|
+
end
|
126
|
+
end
|
@@ -0,0 +1,98 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
require 'ruby-appraiser'
|
4
|
+
require 'optparse'
|
5
|
+
require 'set'
|
6
|
+
|
7
|
+
class RubyAppraiser
|
8
|
+
class CLI
|
9
|
+
def initialize(args = ARGV)
|
10
|
+
@argv = ARGV.dup
|
11
|
+
args = @argv.dup # needed for --git-hook
|
12
|
+
@options = {}
|
13
|
+
adapters = Set.new
|
14
|
+
|
15
|
+
OptionParser.new do |opts|
|
16
|
+
opts.banner = "Usage: #{File::basename($0)} [inspector...] [options]"
|
17
|
+
opts.on('-v', '--[no-]verbose', 'Run verbosely') do |verbose|
|
18
|
+
@options[:verbose] = verbose
|
19
|
+
end
|
20
|
+
opts.on('--list', 'List available adapters') do |list|
|
21
|
+
puts available_adapters
|
22
|
+
exit 1
|
23
|
+
end
|
24
|
+
opts.on('--silent', 'Silence output') do |silent|
|
25
|
+
@options[:silent] = true
|
26
|
+
end
|
27
|
+
opts.on('--mode=MODE',
|
28
|
+
'Set the mode. ' +
|
29
|
+
'[staged,authored,touched,all]') do |mode|
|
30
|
+
@options[:mode] = mode
|
31
|
+
end
|
32
|
+
opts.on('--git-hook',
|
33
|
+
'Output a git hook with current comand to STDOUT') do
|
34
|
+
command = $0
|
35
|
+
if (`which #{File::basename(command)}`).chomp == command
|
36
|
+
command = File::basename(command)
|
37
|
+
end
|
38
|
+
be = 'bundle exec'
|
39
|
+
hook_args = @argv.select { |arg| arg != '--git-hook' }
|
40
|
+
|
41
|
+
indented_git_hook = <<-EOGITHOOK
|
42
|
+
#!/bin/bash
|
43
|
+
echo -e "\\033[0;36mRuby Appraiser: running\\033[0m"
|
44
|
+
|
45
|
+
bundle exec #{command} #{hook_args.join(' ')}
|
46
|
+
|
47
|
+
result_code=$?
|
48
|
+
if [ $result_code -gt "0" ]; then
|
49
|
+
echo -en "\\033[0;31m" # RED
|
50
|
+
echo "[✘] Ruby Appraiser found newly-created defects and "
|
51
|
+
echo " has blocked your commit."
|
52
|
+
echo " Fix the defects and commit again."
|
53
|
+
echo " To bypass, commit again with --no-verify."
|
54
|
+
echo -en "\\033[0m" # RESET
|
55
|
+
exit $result_code
|
56
|
+
else
|
57
|
+
echo -en "\\033[0;32m" # GREEN
|
58
|
+
echo "[✔] Ruby Appraiser ok"
|
59
|
+
echo -en "\\033[0m" #RESET
|
60
|
+
fi
|
61
|
+
EOGITHOOK
|
62
|
+
|
63
|
+
indent = indented_git_hook.scan(/^\s*/).map(&:length).min
|
64
|
+
puts indented_git_hook.lines.map {|l| l[indent..-1]}.join
|
65
|
+
exit 1
|
66
|
+
end
|
67
|
+
opts.on('--all', 'Run all available adapters.') do
|
68
|
+
adapters += available_adapters
|
69
|
+
end
|
70
|
+
end.parse!(args)
|
71
|
+
|
72
|
+
@appraisal = RubyAppraiser::Appraisal.new(options)
|
73
|
+
adapters += args
|
74
|
+
adapters.each do |adapter|
|
75
|
+
@appraisal.add_adapter(adapter) or
|
76
|
+
raise "Unknown adapter '#{adapter}'"
|
77
|
+
end
|
78
|
+
Dir::chdir((`git rev-parse --show-toplevel`).chomp)
|
79
|
+
end
|
80
|
+
|
81
|
+
def options
|
82
|
+
@options.dup
|
83
|
+
end
|
84
|
+
|
85
|
+
def run
|
86
|
+
@appraisal.run!
|
87
|
+
puts @appraisal unless @options[:silent]
|
88
|
+
puts @appraisal.summary if @options[:verbose]
|
89
|
+
@appraisal.success?
|
90
|
+
rescue Object
|
91
|
+
puts "#{@appraisal.class.name} caught #{$!} at #{$!.backtrace.first}."
|
92
|
+
end
|
93
|
+
|
94
|
+
def available_adapters
|
95
|
+
@available_adapters ||= RubyAppraiser::Adapter::all.map(&:adapter_type)
|
96
|
+
end
|
97
|
+
end
|
98
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
class RubyAppraiser
|
4
|
+
class Defect
|
5
|
+
def initialize(file, line, description)
|
6
|
+
@location = [file, line.to_i].freeze
|
7
|
+
@description = description.dup.freeze
|
8
|
+
end
|
9
|
+
|
10
|
+
attr_reader :location
|
11
|
+
attr_reader :description
|
12
|
+
|
13
|
+
def file
|
14
|
+
@location[0]
|
15
|
+
end
|
16
|
+
|
17
|
+
def line
|
18
|
+
@location[1]
|
19
|
+
end
|
20
|
+
|
21
|
+
def to_s
|
22
|
+
"#{file}:#{line} #{description}"
|
23
|
+
end
|
24
|
+
|
25
|
+
def ==(other)
|
26
|
+
self.to_s == other.to_s
|
27
|
+
end
|
28
|
+
|
29
|
+
def <=>(other)
|
30
|
+
self.to_s <=> other.to_s
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
@@ -0,0 +1,41 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
class RubyAppraiser
|
4
|
+
module Git
|
5
|
+
extend self
|
6
|
+
|
7
|
+
def authored_lines(options = {})
|
8
|
+
diff_command = ['git diff']
|
9
|
+
if options[:range]
|
10
|
+
diff_command << options[:range]
|
11
|
+
else
|
12
|
+
diff_command << (options[:staged] ? '--cached' : 'HEAD')
|
13
|
+
end
|
14
|
+
|
15
|
+
diff_output = IO.popen(diff_command.join(' ')) { |io| io.read }
|
16
|
+
|
17
|
+
current_path, current_line = nil, nil
|
18
|
+
authored_lines = Hash.new { |hash, key| hash[key] = [] }
|
19
|
+
|
20
|
+
diff_output.lines do |line|
|
21
|
+
case line
|
22
|
+
when /^---/ then next
|
23
|
+
when /^\+\+\+ (?:b\/)?(.*)/
|
24
|
+
current_path = Regexp::last_match(1)
|
25
|
+
when /-[0-9]+(?:,[0-9]+)? \+([0-9]+)((,[0-9]+)?)/
|
26
|
+
current_line = Regexp::last_match(1).to_i
|
27
|
+
else
|
28
|
+
next if line.start_with? '-'
|
29
|
+
authored_lines[current_path] << current_line if line[0] == '+'
|
30
|
+
current_line += 1 unless current_line.nil?
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
authored_lines.default_proc = Proc.new { [] }
|
35
|
+
authored_lines.reject do |filepath, lines|
|
36
|
+
not File::file? filepath or
|
37
|
+
not RubyAppraiser::rubytype? filepath
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
@@ -0,0 +1,64 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
require 'ruby-appraiser/version'
|
4
|
+
|
5
|
+
class RubyAppraiser
|
6
|
+
|
7
|
+
autoload :Adapter, 'ruby-appraiser/adapter'
|
8
|
+
autoload :Appraisal, 'ruby-appraiser/appraisal'
|
9
|
+
autoload :CLI, 'ruby-appraiser/cli'
|
10
|
+
autoload :Defect, 'ruby-appraiser/defect'
|
11
|
+
autoload :Git, 'ruby-appraiser/git'
|
12
|
+
|
13
|
+
def initialize(options)
|
14
|
+
@options = options.dup
|
15
|
+
end
|
16
|
+
|
17
|
+
def options
|
18
|
+
@options.dup
|
19
|
+
end
|
20
|
+
|
21
|
+
|
22
|
+
def appraisal
|
23
|
+
unless @appraisal
|
24
|
+
@appraisal = Appraisal.new(options)
|
25
|
+
|
26
|
+
unless @appraisal.relevant_files.empty?
|
27
|
+
appraisers(@appraisal).each(&:appraise)
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
@appraisal
|
32
|
+
end
|
33
|
+
|
34
|
+
class << self
|
35
|
+
def rubytypes
|
36
|
+
%w(
|
37
|
+
*.rb
|
38
|
+
*.gemspec
|
39
|
+
Capfile
|
40
|
+
Gemfile
|
41
|
+
Rakefile
|
42
|
+
)
|
43
|
+
end
|
44
|
+
|
45
|
+
def rubytype?(filepath)
|
46
|
+
# true if the extension matches
|
47
|
+
filename = File::basename(filepath)
|
48
|
+
return true if rubytypes.any? do |rubytype|
|
49
|
+
File::fnmatch(rubytype, filename)
|
50
|
+
end
|
51
|
+
|
52
|
+
# true if file has a ruby shebang
|
53
|
+
begin
|
54
|
+
return true if File.open(filepath) do |file|
|
55
|
+
file.read(20).chomp =~ /\A#\!.+ruby/
|
56
|
+
end
|
57
|
+
rescue Errno::ENOENT
|
58
|
+
rescue ArgumentError # invalid byte sequence
|
59
|
+
end
|
60
|
+
|
61
|
+
false
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'ruby-appraiser/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |gem|
|
7
|
+
gem.name = 'ruby-appraiser'
|
8
|
+
gem.version = RubyAppraiser::VERSION
|
9
|
+
gem.authors = ['Ryan Biesemeyer']
|
10
|
+
gem.email = ['ryan@simplymeasured.com']
|
11
|
+
gem.description = 'Run multiple code-quality tools against staged changes'
|
12
|
+
gem.summary = 'A Common interface/executor for code quality tools'
|
13
|
+
gem.homepage = 'https://github.com/simplymeasured'
|
14
|
+
|
15
|
+
gem.files = `git ls-files | grep -v '^ruby-appraiser-'`.split($/)
|
16
|
+
gem.executables = gem.files.grep(%r{^bin/}).map { |f| File.basename(f) }
|
17
|
+
gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
|
18
|
+
gem.require_paths = ['lib']
|
19
|
+
end
|
metadata
ADDED
@@ -0,0 +1,64 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: ruby-appraiser
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 1.0.0
|
5
|
+
prerelease:
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Ryan Biesemeyer
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2013-06-01 00:00:00.000000000 Z
|
13
|
+
dependencies: []
|
14
|
+
description: Run multiple code-quality tools against staged changes
|
15
|
+
email:
|
16
|
+
- ryan@simplymeasured.com
|
17
|
+
executables:
|
18
|
+
- ruby-appraiser
|
19
|
+
extensions: []
|
20
|
+
extra_rdoc_files: []
|
21
|
+
files:
|
22
|
+
- .gitignore
|
23
|
+
- Gemfile
|
24
|
+
- LICENSE-APACHE.md
|
25
|
+
- LICENSE-MIT.md
|
26
|
+
- LICENSE.md
|
27
|
+
- README.md
|
28
|
+
- Rakefile
|
29
|
+
- bin/ruby-appraiser
|
30
|
+
- lib/ruby-appraiser.rb
|
31
|
+
- lib/ruby-appraiser/adapter.rb
|
32
|
+
- lib/ruby-appraiser/adapter/line-length.rb
|
33
|
+
- lib/ruby-appraiser/appraisal.rb
|
34
|
+
- lib/ruby-appraiser/cli.rb
|
35
|
+
- lib/ruby-appraiser/defect.rb
|
36
|
+
- lib/ruby-appraiser/git.rb
|
37
|
+
- lib/ruby-appraiser/version.rb
|
38
|
+
- ruby-appraiser.gemspec
|
39
|
+
homepage: https://github.com/simplymeasured
|
40
|
+
licenses: []
|
41
|
+
post_install_message:
|
42
|
+
rdoc_options: []
|
43
|
+
require_paths:
|
44
|
+
- lib
|
45
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
46
|
+
none: false
|
47
|
+
requirements:
|
48
|
+
- - ! '>='
|
49
|
+
- !ruby/object:Gem::Version
|
50
|
+
version: '0'
|
51
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
52
|
+
none: false
|
53
|
+
requirements:
|
54
|
+
- - ! '>='
|
55
|
+
- !ruby/object:Gem::Version
|
56
|
+
version: '0'
|
57
|
+
requirements: []
|
58
|
+
rubyforge_project:
|
59
|
+
rubygems_version: 1.8.24
|
60
|
+
signing_key:
|
61
|
+
specification_version: 3
|
62
|
+
summary: A Common interface/executor for code quality tools
|
63
|
+
test_files: []
|
64
|
+
has_rdoc:
|