skunk 0.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.
- checksums.yaml +7 -0
- data/.gitignore +12 -0
- data/.reek.yml +28 -0
- data/.rubocop.yml +4 -0
- data/.rubocop_todo.yml +27 -0
- data/Gemfile +8 -0
- data/Gemfile.lock +119 -0
- data/README.md +116 -0
- data/Rakefile +23 -0
- data/bin/console +14 -0
- data/bin/setup +8 -0
- data/exe/skunk +10 -0
- data/lib/skunk.rb +8 -0
- data/lib/skunk/cli/application.rb +26 -0
- data/lib/skunk/cli/command_factory.rb +27 -0
- data/lib/skunk/cli/commands/base.rb +20 -0
- data/lib/skunk/cli/commands/default.rb +39 -0
- data/lib/skunk/cli/commands/help.rb +12 -0
- data/lib/skunk/cli/commands/status_reporter.rb +79 -0
- data/lib/skunk/cli/options.rb +10 -0
- data/lib/skunk/rubycritic/analysed_module.rb +51 -0
- data/lib/skunk/version.rb +5 -0
- data/skunk.gemspec +51 -0
- metadata +223 -0
checksums.yaml
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
SHA256:
|
|
3
|
+
metadata.gz: 9792e73da1c20854f3eb7f9f96af486132bf2e2cca8a7f3aadd6038487ec7ef0
|
|
4
|
+
data.tar.gz: 5cb6a6c3bfd1b009db448b3584a7871452753a124540da96d5b448eec20191e2
|
|
5
|
+
SHA512:
|
|
6
|
+
metadata.gz: 333bf77a14154f09e8ed7f205bfc3b93845788da9ac0088e66b2a3269c858184234cd5efa5eec92371ee74d22e568bff86f360d371622f24e446153bd5d7c89b
|
|
7
|
+
data.tar.gz: 997bad01c5e50c8b936f9364e497dbff9f99cff76c352c51f44f43922f4073ae1378d21177f52c786b3555cb09296eb8daed6b492ddbabfe808c2bcecb9db980
|
data/.gitignore
ADDED
data/.reek.yml
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
# Auto generated by Reeks --todo flag
|
|
2
|
+
---
|
|
3
|
+
detectors:
|
|
4
|
+
UtilityFunction:
|
|
5
|
+
exclude:
|
|
6
|
+
- capture_output_streams
|
|
7
|
+
InstanceVariableAssumption:
|
|
8
|
+
exclude:
|
|
9
|
+
- Skunk::Cli::Application
|
|
10
|
+
- Skunk::Cli::Command::Default
|
|
11
|
+
IrresponsibleModule:
|
|
12
|
+
exclude:
|
|
13
|
+
- Skunk::Cli::Application
|
|
14
|
+
- Skunk::Cli::Command::Default
|
|
15
|
+
- Skunk::Cli::Command::Help
|
|
16
|
+
- Skunk::Command::StatusReporter
|
|
17
|
+
- Skunk::Cli::Options
|
|
18
|
+
- RubyCritic::AnalysedModule
|
|
19
|
+
TooManyStatements:
|
|
20
|
+
exclude:
|
|
21
|
+
- initialize
|
|
22
|
+
- Skunk::Cli::Application#execute
|
|
23
|
+
Attribute:
|
|
24
|
+
exclude:
|
|
25
|
+
- Skunk::Command::StatusReporter#analysed_modules
|
|
26
|
+
FeatureEnvy:
|
|
27
|
+
exclude:
|
|
28
|
+
- Skunk::Command::StatusReporter#table
|
data/.rubocop.yml
ADDED
data/.rubocop_todo.yml
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
# This configuration was generated by
|
|
2
|
+
# `rubocop --auto-gen-config --exclude-limit 500`
|
|
3
|
+
# on 2019-10-15 15:20:00 -0400 using RuboCop version 0.75.1.
|
|
4
|
+
# The point is for the user to remove these configuration records
|
|
5
|
+
# one by one as the offenses are removed from the code base.
|
|
6
|
+
# Note that changes in the inspected code, or installation of new
|
|
7
|
+
# versions of RuboCop, may require this file to be generated again.
|
|
8
|
+
|
|
9
|
+
# Offense count: 1
|
|
10
|
+
# Configuration parameters: CountComments, ExcludedMethods.
|
|
11
|
+
# ExcludedMethods: refine
|
|
12
|
+
Metrics/BlockLength:
|
|
13
|
+
Max: 34
|
|
14
|
+
|
|
15
|
+
# Offense count: 1
|
|
16
|
+
# Cop supports --auto-correct.
|
|
17
|
+
# Configuration parameters: PreferredName.
|
|
18
|
+
Naming/RescuedExceptionsVariableName:
|
|
19
|
+
Exclude:
|
|
20
|
+
- 'lib/skunk/cli/application.rb'
|
|
21
|
+
|
|
22
|
+
# Offense count: 8
|
|
23
|
+
# Cop supports --auto-correct.
|
|
24
|
+
# Configuration parameters: AutoCorrect, AllowHeredoc, AllowURI, URISchemes, IgnoreCopDirectives, IgnoredPatterns.
|
|
25
|
+
# URISchemes: http, https
|
|
26
|
+
Metrics/LineLength:
|
|
27
|
+
Max: 107
|
data/Gemfile
ADDED
data/Gemfile.lock
ADDED
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
PATH
|
|
2
|
+
remote: .
|
|
3
|
+
specs:
|
|
4
|
+
skunk (0.1.0)
|
|
5
|
+
rubycritic-simplecov (~> 4.1.1)
|
|
6
|
+
terminal-table (~> 1.8.0)
|
|
7
|
+
|
|
8
|
+
GEM
|
|
9
|
+
remote: https://rubygems.org/
|
|
10
|
+
specs:
|
|
11
|
+
addressable (2.7.0)
|
|
12
|
+
public_suffix (>= 2.0.2, < 5.0)
|
|
13
|
+
ansi (1.5.0)
|
|
14
|
+
ast (2.4.0)
|
|
15
|
+
axiom-types (0.1.1)
|
|
16
|
+
descendants_tracker (~> 0.0.4)
|
|
17
|
+
ice_nine (~> 0.11.0)
|
|
18
|
+
thread_safe (~> 0.3, >= 0.3.1)
|
|
19
|
+
byebug (11.0.1)
|
|
20
|
+
codeclimate-engine-rb (0.4.1)
|
|
21
|
+
virtus (~> 1.0)
|
|
22
|
+
coercible (1.0.0)
|
|
23
|
+
descendants_tracker (~> 0.0.1)
|
|
24
|
+
descendants_tracker (0.0.4)
|
|
25
|
+
thread_safe (~> 0.3, >= 0.3.1)
|
|
26
|
+
docile (1.3.2)
|
|
27
|
+
equalizer (0.0.11)
|
|
28
|
+
erubis (2.7.0)
|
|
29
|
+
flay (2.12.1)
|
|
30
|
+
erubis (~> 2.7.0)
|
|
31
|
+
path_expander (~> 1.0)
|
|
32
|
+
ruby_parser (~> 3.0)
|
|
33
|
+
sexp_processor (~> 4.0)
|
|
34
|
+
flog (4.6.3)
|
|
35
|
+
path_expander (~> 1.0)
|
|
36
|
+
ruby_parser (~> 3.1, > 3.1.0)
|
|
37
|
+
sexp_processor (~> 4.8)
|
|
38
|
+
ice_nine (0.11.2)
|
|
39
|
+
jaro_winkler (1.5.3)
|
|
40
|
+
json (2.2.0)
|
|
41
|
+
kwalify (0.7.2)
|
|
42
|
+
launchy (2.4.3)
|
|
43
|
+
addressable (~> 2.3)
|
|
44
|
+
minitest (5.8.5)
|
|
45
|
+
minitest-around (0.5.0)
|
|
46
|
+
minitest (~> 5.0)
|
|
47
|
+
parallel (1.18.0)
|
|
48
|
+
parser (2.6.5.0)
|
|
49
|
+
ast (~> 2.4.0)
|
|
50
|
+
path_expander (1.1.0)
|
|
51
|
+
psych (3.1.0)
|
|
52
|
+
public_suffix (4.0.1)
|
|
53
|
+
rainbow (3.0.0)
|
|
54
|
+
rake (10.5.0)
|
|
55
|
+
reek (5.4.0)
|
|
56
|
+
codeclimate-engine-rb (~> 0.4.0)
|
|
57
|
+
kwalify (~> 0.7.0)
|
|
58
|
+
parser (>= 2.5.0.0, < 2.7, != 2.5.1.1)
|
|
59
|
+
psych (~> 3.1.0)
|
|
60
|
+
rainbow (>= 2.0, < 4.0)
|
|
61
|
+
rubocop (0.75.1)
|
|
62
|
+
jaro_winkler (~> 1.5.1)
|
|
63
|
+
parallel (~> 1.10)
|
|
64
|
+
parser (>= 2.6)
|
|
65
|
+
rainbow (>= 2.2.2, < 4.0)
|
|
66
|
+
ruby-progressbar (~> 1.7)
|
|
67
|
+
unicode-display_width (>= 1.4.0, < 1.7)
|
|
68
|
+
ruby-progressbar (1.10.1)
|
|
69
|
+
ruby_parser (3.14.0)
|
|
70
|
+
sexp_processor (~> 4.9)
|
|
71
|
+
rubycritic-simplecov (4.1.1)
|
|
72
|
+
flay (~> 2.8)
|
|
73
|
+
flog (~> 4.4)
|
|
74
|
+
launchy (= 2.4.3)
|
|
75
|
+
parser (~> 2.6.0)
|
|
76
|
+
rainbow (~> 3.0)
|
|
77
|
+
reek (~> 5.0, < 6.0)
|
|
78
|
+
ruby_parser (~> 3.8)
|
|
79
|
+
simplecov (~> 0.17.0)
|
|
80
|
+
tty-which (~> 0.4.0)
|
|
81
|
+
virtus (~> 1.0)
|
|
82
|
+
sexp_processor (4.13.0)
|
|
83
|
+
simplecov (0.17.1)
|
|
84
|
+
docile (~> 1.1)
|
|
85
|
+
json (>= 1.8, < 3)
|
|
86
|
+
simplecov-html (~> 0.10.0)
|
|
87
|
+
simplecov-console (0.5.0)
|
|
88
|
+
ansi
|
|
89
|
+
simplecov
|
|
90
|
+
terminal-table
|
|
91
|
+
simplecov-html (0.10.2)
|
|
92
|
+
terminal-table (1.8.0)
|
|
93
|
+
unicode-display_width (~> 1.1, >= 1.1.1)
|
|
94
|
+
thread_safe (0.3.6)
|
|
95
|
+
tty-which (0.4.1)
|
|
96
|
+
unicode-display_width (1.6.0)
|
|
97
|
+
virtus (1.0.5)
|
|
98
|
+
axiom-types (~> 0.1)
|
|
99
|
+
coercible (~> 1.0)
|
|
100
|
+
descendants_tracker (~> 0.0, >= 0.0.3)
|
|
101
|
+
equalizer (~> 0.0, >= 0.0.9)
|
|
102
|
+
|
|
103
|
+
PLATFORMS
|
|
104
|
+
ruby
|
|
105
|
+
|
|
106
|
+
DEPENDENCIES
|
|
107
|
+
bundler (~> 2.0)
|
|
108
|
+
byebug (~> 11)
|
|
109
|
+
minitest (~> 5.8.4)
|
|
110
|
+
minitest-around (~> 0.5.0)
|
|
111
|
+
rake (~> 10.0)
|
|
112
|
+
reek (~> 5.4.0)
|
|
113
|
+
rubocop (< 1.0)
|
|
114
|
+
simplecov (~> 0.17.1)
|
|
115
|
+
simplecov-console (= 0.5.0)
|
|
116
|
+
skunk!
|
|
117
|
+
|
|
118
|
+
BUNDLED WITH
|
|
119
|
+
2.0.2
|
data/README.md
ADDED
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
# Skunk
|
|
2
|
+
|
|
3
|
+
A RubyCritic extension to calculate StinkScore for a file or project.
|
|
4
|
+
|
|
5
|
+
## What is the StinkScore?
|
|
6
|
+
|
|
7
|
+
The StinkScore is a value that assess the quality of a module. It takes into
|
|
8
|
+
account:
|
|
9
|
+
|
|
10
|
+
- Code Quality
|
|
11
|
+
- Code Smells
|
|
12
|
+
- Churn
|
|
13
|
+
- Code Coverage
|
|
14
|
+
|
|
15
|
+
The formula is not perfect and it is certainly controversial.
|
|
16
|
+
|
|
17
|
+
## Installation
|
|
18
|
+
|
|
19
|
+
Add this line to your application's Gemfile:
|
|
20
|
+
|
|
21
|
+
```ruby
|
|
22
|
+
gem 'skunk'
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
And then execute:
|
|
26
|
+
|
|
27
|
+
$ bundle
|
|
28
|
+
|
|
29
|
+
Or install it yourself as:
|
|
30
|
+
|
|
31
|
+
$ gem install skunk
|
|
32
|
+
|
|
33
|
+
## Usage
|
|
34
|
+
|
|
35
|
+
Simply run:
|
|
36
|
+
|
|
37
|
+
```
|
|
38
|
+
skunk
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
Then get a list of stinky files:
|
|
42
|
+
|
|
43
|
+
```
|
|
44
|
+
$ skunk
|
|
45
|
+
warning: parser/current is loading parser/ruby26, which recognizes
|
|
46
|
+
warning: 2.6.5-compliant syntax, but you are running 2.6.2.
|
|
47
|
+
warning: please see https://github.com/whitequark/parser#compatibility-with-ruby-mri.
|
|
48
|
+
running flay smells
|
|
49
|
+
|
|
50
|
+
running flog smells
|
|
51
|
+
.............
|
|
52
|
+
running reek smells
|
|
53
|
+
.............
|
|
54
|
+
running complexity
|
|
55
|
+
.............
|
|
56
|
+
running attributes
|
|
57
|
+
.............
|
|
58
|
+
running churn
|
|
59
|
+
.............
|
|
60
|
+
running simple_cov
|
|
61
|
+
.............
|
|
62
|
+
New critique at file:////Users/etagwerker/Projects/fastruby/skunk/tmp/rubycritic/overview.html
|
|
63
|
+
+-----------------------------------------------------+----------------------------+----------------------------+----------------------------+----------------------------+----------------------------+
|
|
64
|
+
| file | stink_score | churn_times_cost | churn | cost | coverage |
|
|
65
|
+
+-----------------------------------------------------+----------------------------+----------------------------+----------------------------+----------------------------+----------------------------+
|
|
66
|
+
| lib/skunk/cli/commands/default.rb | 166.44 | 1.6643999999999999 | 3 | 0.5548 | 0 |
|
|
67
|
+
| lib/skunk/cli/application.rb | 139.2 | 1.392 | 3 | 0.46399999999999997 | 0 |
|
|
68
|
+
| lib/skunk/cli/command_factory.rb | 97.6 | 0.976 | 2 | 0.488 | 0 |
|
|
69
|
+
| test/test_helper.rb | 75.2 | 0.752 | 2 | 0.376 | 0 |
|
|
70
|
+
| lib/skunk/rubycritic/analysed_module.rb | 48.12 | 1.7184 | 2 | 0.8592 | 72.72727272727273 |
|
|
71
|
+
| test/lib/skunk/cli/commands/status_reporter_test.rb | 45.6 | 0.456 | 1 | 0.456 | 0 |
|
|
72
|
+
| lib/skunk/cli/commands/base.rb | 29.52 | 0.2952 | 3 | 0.0984 | 0 |
|
|
73
|
+
| lib/skunk/cli/commands/status_reporter.rb | 8.0 | 7.9956 | 3 | 2.6652 | 100.0 |
|
|
74
|
+
| test/lib/skunk/rubycritic/analysed_module_test.rb | 2.63 | 2.6312 | 2 | 1.3156 | 100.0 |
|
|
75
|
+
| lib/skunk.rb | 0.0 | 0.0 | 2 | 0.0 | 0 |
|
|
76
|
+
| lib/skunk/cli/options.rb | 0.0 | 0.0 | 2 | 0.0 | 0 |
|
|
77
|
+
| lib/skunk/version.rb | 0.0 | 0.0 | 2 | 0.0 | 0 |
|
|
78
|
+
| lib/skunk/cli/commands/help.rb | 0.0 | 0.0 | 2 | 0.0 | 0 |
|
|
79
|
+
+-----------------------------------------------------+----------------------------+----------------------------+----------------------------+----------------------------+----------------------------+
|
|
80
|
+
|
|
81
|
+
StinkScore Total: 612.31
|
|
82
|
+
Modules Analysed: 13
|
|
83
|
+
StinkScore Average: 0.47100769230769230769230769231e2
|
|
84
|
+
Worst StinkScore: 166.44 (lib/skunk/cli/commands/default.rb)
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
The command will run `rubycritic`, so that will generate a RubyCritic overview
|
|
88
|
+
HTML report as well.
|
|
89
|
+
|
|
90
|
+
Skunk's report will be in the console. Use it wisely. :)
|
|
91
|
+
|
|
92
|
+
## Known Issues
|
|
93
|
+
|
|
94
|
+
The StinkScore should be calculated per method. This would provide a more accurate
|
|
95
|
+
representation of the average StinkScore in a module.
|
|
96
|
+
|
|
97
|
+
I think that the StinkScore of a module should be the average of the StinkScores of
|
|
98
|
+
all of its methods.
|
|
99
|
+
|
|
100
|
+
Right now the StinkScore is calculated using the totals for a module:
|
|
101
|
+
|
|
102
|
+
- Total Code Coverage Percentage per Module
|
|
103
|
+
- Total Churn per Module
|
|
104
|
+
- Total Cost per Module
|
|
105
|
+
|
|
106
|
+
For more details, feel free to review and improve this method: [RubyCritic::AnalysedModule#stink_score]
|
|
107
|
+
|
|
108
|
+
## Development
|
|
109
|
+
|
|
110
|
+
After checking out the repo, run `bin/setup` to install dependencies. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
|
|
111
|
+
|
|
112
|
+
To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org).
|
|
113
|
+
|
|
114
|
+
## Contributing
|
|
115
|
+
|
|
116
|
+
Bug reports and pull requests are welcome on GitHub at https://github.com/fastruby/skunk.
|
data/Rakefile
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "bundler/gem_tasks"
|
|
4
|
+
require "rake/testtask"
|
|
5
|
+
require "rubocop/rake_task"
|
|
6
|
+
require "reek/rake/task"
|
|
7
|
+
require "rubycritic/rake_task"
|
|
8
|
+
|
|
9
|
+
Rake::TestTask.new do |task|
|
|
10
|
+
task.libs.push "lib"
|
|
11
|
+
task.libs.push "test"
|
|
12
|
+
task.pattern = "test/**/*_test.rb"
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
RuboCop::RakeTask.new
|
|
16
|
+
|
|
17
|
+
Reek::Rake::Task.new
|
|
18
|
+
|
|
19
|
+
RubyCritic::RakeTask.new do |task|
|
|
20
|
+
task.paths = FileList["lib/**/*.rb"]
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
task default: %i[test reek rubocop]
|
data/bin/console
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
#!/usr/bin/env ruby
|
|
2
|
+
|
|
3
|
+
require "bundler/setup"
|
|
4
|
+
require "skunk"
|
|
5
|
+
|
|
6
|
+
# You can add fixtures and/or initialization code here to make experimenting
|
|
7
|
+
# with your gem easier. You can also use a different console, if you like.
|
|
8
|
+
|
|
9
|
+
# (If you use this, don't forget to add pry to your Gemfile!)
|
|
10
|
+
# require "pry"
|
|
11
|
+
# Pry.start
|
|
12
|
+
|
|
13
|
+
require "irb"
|
|
14
|
+
IRB.start(__FILE__)
|
data/bin/setup
ADDED
data/exe/skunk
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
#!/usr/bin/env ruby
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
# Always look in the lib directory of this gem
|
|
5
|
+
# first when searching the load path
|
|
6
|
+
$LOAD_PATH.unshift File.expand_path("../lib", __dir__)
|
|
7
|
+
|
|
8
|
+
require "skunk/cli/application"
|
|
9
|
+
|
|
10
|
+
exit Skunk::Cli::Application.new(ARGV).execute
|
data/lib/skunk.rb
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "skunk"
|
|
4
|
+
require "skunk/rubycritic/analysed_module"
|
|
5
|
+
require "skunk/cli/options"
|
|
6
|
+
require "skunk/cli/command_factory"
|
|
7
|
+
|
|
8
|
+
require "rubycritic/cli/application"
|
|
9
|
+
|
|
10
|
+
module Skunk
|
|
11
|
+
module Cli
|
|
12
|
+
# Knows how to execute command line commands
|
|
13
|
+
class Application < RubyCritic::Cli::Application
|
|
14
|
+
def execute
|
|
15
|
+
parsed_options = @options.parse.to_h
|
|
16
|
+
|
|
17
|
+
reporter = Skunk::Cli::CommandFactory.create(parsed_options).execute
|
|
18
|
+
print(reporter.status_message)
|
|
19
|
+
reporter.status
|
|
20
|
+
rescue OptionParser::InvalidOption => error
|
|
21
|
+
warn "Error: #{error}"
|
|
22
|
+
STATUS_ERROR
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
end
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "rubycritic/command_factory"
|
|
4
|
+
|
|
5
|
+
module Skunk
|
|
6
|
+
module Cli
|
|
7
|
+
# Knows how to calculate the command that was request by the CLI user
|
|
8
|
+
class CommandFactory < RubyCritic::CommandFactory
|
|
9
|
+
COMMAND_CLASS_MODES = %i[version help default].freeze
|
|
10
|
+
|
|
11
|
+
# Returns the command class based on the command that was executed
|
|
12
|
+
#
|
|
13
|
+
# @param mode
|
|
14
|
+
# @return [Class]
|
|
15
|
+
def self.command_class(mode)
|
|
16
|
+
mode = mode.to_s.split("_").first.to_sym
|
|
17
|
+
if COMMAND_CLASS_MODES.include? mode
|
|
18
|
+
require "skunk/cli/commands/#{mode}"
|
|
19
|
+
Command.const_get(mode.capitalize)
|
|
20
|
+
else
|
|
21
|
+
require "skunk/cli/commands/default"
|
|
22
|
+
Command::Default
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
end
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "rubycritic/commands/base"
|
|
4
|
+
require "skunk/cli/commands/status_reporter"
|
|
5
|
+
|
|
6
|
+
module Skunk
|
|
7
|
+
module Cli
|
|
8
|
+
module Command
|
|
9
|
+
# Base class for `Skunk` commands. It knows how to build a command with
|
|
10
|
+
# options. It always uses a [Skunk::Command::StatusReporter] as its
|
|
11
|
+
# reporter object.
|
|
12
|
+
class Base < RubyCritic::Command::Base
|
|
13
|
+
def initialize(options)
|
|
14
|
+
@options = options
|
|
15
|
+
@status_reporter = Skunk::Command::StatusReporter.new(@options)
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
end
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "rubycritic/commands/default"
|
|
4
|
+
require "rubycritic/analysers_runner"
|
|
5
|
+
require "rubycritic/revision_comparator"
|
|
6
|
+
require "rubycritic/reporter"
|
|
7
|
+
|
|
8
|
+
require "skunk/cli/commands/base"
|
|
9
|
+
require "skunk/cli/commands/status_reporter"
|
|
10
|
+
|
|
11
|
+
module Skunk
|
|
12
|
+
module Cli
|
|
13
|
+
module Command
|
|
14
|
+
# Default command runs a critique using RubyCritic and uses
|
|
15
|
+
# Skunk::Command::StatusReporter to report status
|
|
16
|
+
class Default < RubyCritic::Command::Default
|
|
17
|
+
def initialize(options)
|
|
18
|
+
super
|
|
19
|
+
@status_reporter = Skunk::Command::StatusReporter.new(@options)
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def execute
|
|
23
|
+
report(critique)
|
|
24
|
+
status_reporter
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def critique
|
|
28
|
+
RubyCritic::AnalysersRunner.new(paths).run
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def report(analysed_modules)
|
|
32
|
+
RubyCritic::Reporter.generate_report(analysed_modules)
|
|
33
|
+
status_reporter.analysed_modules = analysed_modules
|
|
34
|
+
status_reporter.score = analysed_modules.score
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
end
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "rubycritic/commands/status_reporter"
|
|
4
|
+
require "terminal-table"
|
|
5
|
+
|
|
6
|
+
module Skunk
|
|
7
|
+
module Command
|
|
8
|
+
# Knows how to report status for stinky files
|
|
9
|
+
class StatusReporter < RubyCritic::Command::StatusReporter
|
|
10
|
+
attr_accessor :analysed_modules
|
|
11
|
+
|
|
12
|
+
HEADINGS = %w[file stink_score churn_times_cost churn cost coverage].freeze
|
|
13
|
+
|
|
14
|
+
# Returns a status message with a table of all analysed_modules and
|
|
15
|
+
# a stink score average
|
|
16
|
+
def update_status_message
|
|
17
|
+
opts = table_options.merge(headings: HEADINGS, rows: table)
|
|
18
|
+
|
|
19
|
+
ttable = Terminal::Table.new(opts)
|
|
20
|
+
|
|
21
|
+
@status_message = "#{ttable}\n\n"
|
|
22
|
+
|
|
23
|
+
@status_message += "StinkScore Total: #{total_stink_score}\n"
|
|
24
|
+
@status_message += "Modules Analysed: #{analysed_modules_count}\n"
|
|
25
|
+
@status_message += "StinkScore Average: #{stink_score}\n"
|
|
26
|
+
@status_message += "Worst StinkScore: #{worst.stink_score} (#{worst.pathname})\n" if worst
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
private
|
|
30
|
+
|
|
31
|
+
def analysed_modules_count
|
|
32
|
+
@analysed_modules_count ||= analysed_modules.count
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def worst
|
|
36
|
+
@worst ||= sorted_modules.first
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def sorted_modules
|
|
40
|
+
@sorted_modules ||= analysed_modules.sort_by(&:stink_score).reverse!
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def total_stink_score
|
|
44
|
+
@total_stink_score ||= analysed_modules.map(&:stink_score).inject(0.0, :+)
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def total_churn_times_cost
|
|
48
|
+
analysed_modules.map(&:churn_times_cost).sum
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
def stink_score
|
|
52
|
+
return 0 if analysed_modules_count == 0
|
|
53
|
+
|
|
54
|
+
total_stink_score.to_d / analysed_modules_count
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
def table_options
|
|
58
|
+
{
|
|
59
|
+
style: {
|
|
60
|
+
width: 200
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
def table
|
|
66
|
+
sorted_modules.map do |a_mod|
|
|
67
|
+
[
|
|
68
|
+
a_mod.pathname,
|
|
69
|
+
a_mod.stink_score,
|
|
70
|
+
a_mod.churn_times_cost,
|
|
71
|
+
a_mod.churn,
|
|
72
|
+
a_mod.cost,
|
|
73
|
+
a_mod.coverage
|
|
74
|
+
]
|
|
75
|
+
end
|
|
76
|
+
end
|
|
77
|
+
end
|
|
78
|
+
end
|
|
79
|
+
end
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "rubycritic/core/analysed_module"
|
|
4
|
+
|
|
5
|
+
module RubyCritic
|
|
6
|
+
# Monkey-patches RubyCritic::AnalysedModule to add a stink_score method
|
|
7
|
+
class AnalysedModule
|
|
8
|
+
PERFECT_COVERAGE = 100
|
|
9
|
+
|
|
10
|
+
# Returns a numeric value that represents the stink_score of a module:
|
|
11
|
+
#
|
|
12
|
+
# If module is perfectly covered, stink score is the same as the
|
|
13
|
+
# `churn_times_cost`
|
|
14
|
+
#
|
|
15
|
+
# If module has no coverage, stink score is a penalized value of
|
|
16
|
+
# `churn_times_cost`
|
|
17
|
+
#
|
|
18
|
+
# For now the stink_score is calculated by multiplying `churn_times_cost`
|
|
19
|
+
# times the lack of coverage.
|
|
20
|
+
#
|
|
21
|
+
# For example:
|
|
22
|
+
#
|
|
23
|
+
# When `churn_times_cost` is 100 and module is perfectly covered:
|
|
24
|
+
# stink_score => 100
|
|
25
|
+
#
|
|
26
|
+
# When `churn_times_cost` is 100 and module is not covered at all:
|
|
27
|
+
# stink_score => 100 * 100 = 10_000
|
|
28
|
+
#
|
|
29
|
+
# When `churn_times_cost` is 100 and module is covered at 75%:
|
|
30
|
+
# stink_score => 100 * 25 (percentage uncovered) = 2_500
|
|
31
|
+
#
|
|
32
|
+
# @return [Float]
|
|
33
|
+
def stink_score
|
|
34
|
+
return churn_times_cost.round(2) if coverage == PERFECT_COVERAGE
|
|
35
|
+
|
|
36
|
+
(churn_times_cost * (PERFECT_COVERAGE - coverage.to_i)).round(2)
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
# Returns the value of churn times cost.
|
|
40
|
+
#
|
|
41
|
+
# @return [Integer]
|
|
42
|
+
def churn_times_cost
|
|
43
|
+
safe_churn = churn > 0 ? churn : 1
|
|
44
|
+
@churn_times_cost ||= safe_churn * cost
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def method_name
|
|
48
|
+
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
end
|
data/skunk.gemspec
ADDED
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
lib = File.expand_path("lib", __dir__)
|
|
4
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
|
5
|
+
require "skunk/version"
|
|
6
|
+
|
|
7
|
+
Gem::Specification.new do |spec|
|
|
8
|
+
spec.name = "skunk"
|
|
9
|
+
spec.version = Skunk::VERSION
|
|
10
|
+
spec.authors = ["Ernesto Tagwerker"]
|
|
11
|
+
spec.email = ["ernesto+github@ombulabs.com"]
|
|
12
|
+
|
|
13
|
+
spec.summary = "A library to assess code quality vs. code coverage"
|
|
14
|
+
spec.description = "Knows how to calculate the StinkScore for a Ruby file"
|
|
15
|
+
spec.homepage = "https://github.com/fastruby/skunk"
|
|
16
|
+
|
|
17
|
+
# Prevent pushing this gem to RubyGems.org. To allow pushes either set the 'allowed_push_host'
|
|
18
|
+
# to allow pushing to a single host or delete this section to allow pushing to any host.
|
|
19
|
+
if spec.respond_to?(:metadata)
|
|
20
|
+
spec.metadata["allowed_push_host"] = "https://www.rubygems.org"
|
|
21
|
+
|
|
22
|
+
spec.metadata["homepage_uri"] = spec.homepage
|
|
23
|
+
spec.metadata["source_code_uri"] = "https://github.com/fastruby/skunk"
|
|
24
|
+
spec.metadata["changelog_uri"] = "https://github.com/fastruby/skunk/changelog"
|
|
25
|
+
else
|
|
26
|
+
raise "RubyGems 2.0 or newer is required to protect against " \
|
|
27
|
+
"public gem pushes."
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
# Specify which files should be added to the gem when it is released.
|
|
31
|
+
# The `git ls-files -z` loads the files in the RubyGem that have been added into git.
|
|
32
|
+
spec.files = Dir.chdir(File.expand_path(__dir__)) do
|
|
33
|
+
`git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
|
|
34
|
+
end
|
|
35
|
+
spec.bindir = "exe"
|
|
36
|
+
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
|
|
37
|
+
spec.require_paths = ["lib"]
|
|
38
|
+
|
|
39
|
+
spec.add_dependency "rubycritic-simplecov", "~> 4.1.1"
|
|
40
|
+
spec.add_dependency "terminal-table", "~> 1.8.0"
|
|
41
|
+
|
|
42
|
+
spec.add_development_dependency "bundler", "~> 2.0"
|
|
43
|
+
spec.add_development_dependency "byebug", "~> 11"
|
|
44
|
+
spec.add_development_dependency "minitest", "~> 5.8.4"
|
|
45
|
+
spec.add_development_dependency "minitest-around", "~> 0.5.0"
|
|
46
|
+
spec.add_development_dependency "rake", "~> 10.0"
|
|
47
|
+
spec.add_development_dependency "reek", "~> 5.4.0"
|
|
48
|
+
spec.add_development_dependency "rubocop", "< 1.0"
|
|
49
|
+
spec.add_development_dependency "simplecov", "~> 0.17.1"
|
|
50
|
+
spec.add_development_dependency "simplecov-console", "0.5.0"
|
|
51
|
+
end
|
metadata
ADDED
|
@@ -0,0 +1,223 @@
|
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
|
2
|
+
name: skunk
|
|
3
|
+
version: !ruby/object:Gem::Version
|
|
4
|
+
version: 0.1.0
|
|
5
|
+
platform: ruby
|
|
6
|
+
authors:
|
|
7
|
+
- Ernesto Tagwerker
|
|
8
|
+
autorequire:
|
|
9
|
+
bindir: exe
|
|
10
|
+
cert_chain: []
|
|
11
|
+
date: 2019-10-16 00:00:00.000000000 Z
|
|
12
|
+
dependencies:
|
|
13
|
+
- !ruby/object:Gem::Dependency
|
|
14
|
+
name: rubycritic-simplecov
|
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
|
16
|
+
requirements:
|
|
17
|
+
- - "~>"
|
|
18
|
+
- !ruby/object:Gem::Version
|
|
19
|
+
version: 4.1.1
|
|
20
|
+
type: :runtime
|
|
21
|
+
prerelease: false
|
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
23
|
+
requirements:
|
|
24
|
+
- - "~>"
|
|
25
|
+
- !ruby/object:Gem::Version
|
|
26
|
+
version: 4.1.1
|
|
27
|
+
- !ruby/object:Gem::Dependency
|
|
28
|
+
name: terminal-table
|
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
|
30
|
+
requirements:
|
|
31
|
+
- - "~>"
|
|
32
|
+
- !ruby/object:Gem::Version
|
|
33
|
+
version: 1.8.0
|
|
34
|
+
type: :runtime
|
|
35
|
+
prerelease: false
|
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
37
|
+
requirements:
|
|
38
|
+
- - "~>"
|
|
39
|
+
- !ruby/object:Gem::Version
|
|
40
|
+
version: 1.8.0
|
|
41
|
+
- !ruby/object:Gem::Dependency
|
|
42
|
+
name: bundler
|
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
|
44
|
+
requirements:
|
|
45
|
+
- - "~>"
|
|
46
|
+
- !ruby/object:Gem::Version
|
|
47
|
+
version: '2.0'
|
|
48
|
+
type: :development
|
|
49
|
+
prerelease: false
|
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
51
|
+
requirements:
|
|
52
|
+
- - "~>"
|
|
53
|
+
- !ruby/object:Gem::Version
|
|
54
|
+
version: '2.0'
|
|
55
|
+
- !ruby/object:Gem::Dependency
|
|
56
|
+
name: byebug
|
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
|
58
|
+
requirements:
|
|
59
|
+
- - "~>"
|
|
60
|
+
- !ruby/object:Gem::Version
|
|
61
|
+
version: '11'
|
|
62
|
+
type: :development
|
|
63
|
+
prerelease: false
|
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
65
|
+
requirements:
|
|
66
|
+
- - "~>"
|
|
67
|
+
- !ruby/object:Gem::Version
|
|
68
|
+
version: '11'
|
|
69
|
+
- !ruby/object:Gem::Dependency
|
|
70
|
+
name: minitest
|
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
|
72
|
+
requirements:
|
|
73
|
+
- - "~>"
|
|
74
|
+
- !ruby/object:Gem::Version
|
|
75
|
+
version: 5.8.4
|
|
76
|
+
type: :development
|
|
77
|
+
prerelease: false
|
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
79
|
+
requirements:
|
|
80
|
+
- - "~>"
|
|
81
|
+
- !ruby/object:Gem::Version
|
|
82
|
+
version: 5.8.4
|
|
83
|
+
- !ruby/object:Gem::Dependency
|
|
84
|
+
name: minitest-around
|
|
85
|
+
requirement: !ruby/object:Gem::Requirement
|
|
86
|
+
requirements:
|
|
87
|
+
- - "~>"
|
|
88
|
+
- !ruby/object:Gem::Version
|
|
89
|
+
version: 0.5.0
|
|
90
|
+
type: :development
|
|
91
|
+
prerelease: false
|
|
92
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
93
|
+
requirements:
|
|
94
|
+
- - "~>"
|
|
95
|
+
- !ruby/object:Gem::Version
|
|
96
|
+
version: 0.5.0
|
|
97
|
+
- !ruby/object:Gem::Dependency
|
|
98
|
+
name: rake
|
|
99
|
+
requirement: !ruby/object:Gem::Requirement
|
|
100
|
+
requirements:
|
|
101
|
+
- - "~>"
|
|
102
|
+
- !ruby/object:Gem::Version
|
|
103
|
+
version: '10.0'
|
|
104
|
+
type: :development
|
|
105
|
+
prerelease: false
|
|
106
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
107
|
+
requirements:
|
|
108
|
+
- - "~>"
|
|
109
|
+
- !ruby/object:Gem::Version
|
|
110
|
+
version: '10.0'
|
|
111
|
+
- !ruby/object:Gem::Dependency
|
|
112
|
+
name: reek
|
|
113
|
+
requirement: !ruby/object:Gem::Requirement
|
|
114
|
+
requirements:
|
|
115
|
+
- - "~>"
|
|
116
|
+
- !ruby/object:Gem::Version
|
|
117
|
+
version: 5.4.0
|
|
118
|
+
type: :development
|
|
119
|
+
prerelease: false
|
|
120
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
121
|
+
requirements:
|
|
122
|
+
- - "~>"
|
|
123
|
+
- !ruby/object:Gem::Version
|
|
124
|
+
version: 5.4.0
|
|
125
|
+
- !ruby/object:Gem::Dependency
|
|
126
|
+
name: rubocop
|
|
127
|
+
requirement: !ruby/object:Gem::Requirement
|
|
128
|
+
requirements:
|
|
129
|
+
- - "<"
|
|
130
|
+
- !ruby/object:Gem::Version
|
|
131
|
+
version: '1.0'
|
|
132
|
+
type: :development
|
|
133
|
+
prerelease: false
|
|
134
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
135
|
+
requirements:
|
|
136
|
+
- - "<"
|
|
137
|
+
- !ruby/object:Gem::Version
|
|
138
|
+
version: '1.0'
|
|
139
|
+
- !ruby/object:Gem::Dependency
|
|
140
|
+
name: simplecov
|
|
141
|
+
requirement: !ruby/object:Gem::Requirement
|
|
142
|
+
requirements:
|
|
143
|
+
- - "~>"
|
|
144
|
+
- !ruby/object:Gem::Version
|
|
145
|
+
version: 0.17.1
|
|
146
|
+
type: :development
|
|
147
|
+
prerelease: false
|
|
148
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
149
|
+
requirements:
|
|
150
|
+
- - "~>"
|
|
151
|
+
- !ruby/object:Gem::Version
|
|
152
|
+
version: 0.17.1
|
|
153
|
+
- !ruby/object:Gem::Dependency
|
|
154
|
+
name: simplecov-console
|
|
155
|
+
requirement: !ruby/object:Gem::Requirement
|
|
156
|
+
requirements:
|
|
157
|
+
- - '='
|
|
158
|
+
- !ruby/object:Gem::Version
|
|
159
|
+
version: 0.5.0
|
|
160
|
+
type: :development
|
|
161
|
+
prerelease: false
|
|
162
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
163
|
+
requirements:
|
|
164
|
+
- - '='
|
|
165
|
+
- !ruby/object:Gem::Version
|
|
166
|
+
version: 0.5.0
|
|
167
|
+
description: Knows how to calculate the StinkScore for a Ruby file
|
|
168
|
+
email:
|
|
169
|
+
- ernesto+github@ombulabs.com
|
|
170
|
+
executables:
|
|
171
|
+
- skunk
|
|
172
|
+
extensions: []
|
|
173
|
+
extra_rdoc_files: []
|
|
174
|
+
files:
|
|
175
|
+
- ".gitignore"
|
|
176
|
+
- ".reek.yml"
|
|
177
|
+
- ".rubocop.yml"
|
|
178
|
+
- ".rubocop_todo.yml"
|
|
179
|
+
- Gemfile
|
|
180
|
+
- Gemfile.lock
|
|
181
|
+
- README.md
|
|
182
|
+
- Rakefile
|
|
183
|
+
- bin/console
|
|
184
|
+
- bin/setup
|
|
185
|
+
- exe/skunk
|
|
186
|
+
- lib/skunk.rb
|
|
187
|
+
- lib/skunk/cli/application.rb
|
|
188
|
+
- lib/skunk/cli/command_factory.rb
|
|
189
|
+
- lib/skunk/cli/commands/base.rb
|
|
190
|
+
- lib/skunk/cli/commands/default.rb
|
|
191
|
+
- lib/skunk/cli/commands/help.rb
|
|
192
|
+
- lib/skunk/cli/commands/status_reporter.rb
|
|
193
|
+
- lib/skunk/cli/options.rb
|
|
194
|
+
- lib/skunk/rubycritic/analysed_module.rb
|
|
195
|
+
- lib/skunk/version.rb
|
|
196
|
+
- skunk.gemspec
|
|
197
|
+
homepage: https://github.com/fastruby/skunk
|
|
198
|
+
licenses: []
|
|
199
|
+
metadata:
|
|
200
|
+
allowed_push_host: https://www.rubygems.org
|
|
201
|
+
homepage_uri: https://github.com/fastruby/skunk
|
|
202
|
+
source_code_uri: https://github.com/fastruby/skunk
|
|
203
|
+
changelog_uri: https://github.com/fastruby/skunk/changelog
|
|
204
|
+
post_install_message:
|
|
205
|
+
rdoc_options: []
|
|
206
|
+
require_paths:
|
|
207
|
+
- lib
|
|
208
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
|
209
|
+
requirements:
|
|
210
|
+
- - ">="
|
|
211
|
+
- !ruby/object:Gem::Version
|
|
212
|
+
version: '0'
|
|
213
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
214
|
+
requirements:
|
|
215
|
+
- - ">="
|
|
216
|
+
- !ruby/object:Gem::Version
|
|
217
|
+
version: '0'
|
|
218
|
+
requirements: []
|
|
219
|
+
rubygems_version: 3.0.3
|
|
220
|
+
signing_key:
|
|
221
|
+
specification_version: 4
|
|
222
|
+
summary: A library to assess code quality vs. code coverage
|
|
223
|
+
test_files: []
|