debt_ceiling 0.0.6 → 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.
- data/.travis.yml +5 -0
- data/MIT-LICENSE.md +22 -0
- data/README.md +44 -48
- data/Rakefile +2 -2
- data/bin/debt_ceiling +2 -2
- data/debt_ceiling.gemspec +17 -17
- data/examples/.debt_ceiling.rb.example +27 -0
- data/lib/debt_ceiling/accounting.rb +9 -8
- data/lib/debt_ceiling/debt.rb +39 -31
- data/lib/debt_ceiling/version.rb +1 -1
- data/lib/debt_ceiling.rb +40 -50
- data/spec/debt_ceiling_spec.rb +36 -0
- data/spec/spec_helper.rb +4 -0
- data/spec/support/manual_example.rb +4 -0
- data/spec/support/todo_example.rb +3 -0
- metadata +59 -26
- checksums.yaml +0 -7
- data/examples/.debt_ceiling.example +0 -25
- data/test/debt_ceiling_test.rb +0 -5
- data/test/test_helper.rb +0 -3
data/.travis.yml
ADDED
data/MIT-LICENSE.md
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
The MIT License (MIT)
|
2
|
+
=====================
|
3
|
+
|
4
|
+
Copyright (c) 2014 Brian Glusman <bglusman@shutterstock.com>
|
5
|
+
|
6
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy of
|
7
|
+
this software and associated documentation files (the "Software"), to deal in
|
8
|
+
the Software without restriction, including without limitation the rights to
|
9
|
+
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
|
10
|
+
of the Software, and to permit persons to whom the Software is furnished to do
|
11
|
+
so, subject to the following conditions:
|
12
|
+
|
13
|
+
The above copyright notice and this permission notice shall be included in all
|
14
|
+
copies or substantial portions of the Software.
|
15
|
+
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
17
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
18
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
19
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
20
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
21
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
22
|
+
SOFTWARE.
|
data/README.md
CHANGED
@@ -1,82 +1,78 @@
|
|
1
|
+
[](http://badge.fury.io/rb/debt_ceiling)
|
2
|
+
[](https://travis-ci.org/bglusman/debt_ceiling)
|
1
3
|
[](https://gitter.im/bglusman/debt_ceiling)
|
4
|
+
[](https://www.omniref.com/ruby/gems/debt_ceiling)
|
2
5
|
|
3
6
|
#DebtCeiling
|
4
7
|
|
5
|
-
|
8
|
+
Main goal is to enforce a technical debt ceiling and tech debt reduction deadlines for your Ruby project programmatically via a configurable combination of static analysis and/or manual assignment/recognition from explicit source code references as part of your application's test suite. Eventually perhaps will aid in visualizing this quantification as a graph or graphs, and breaking down debt into various categories and sources. Currently it highlights the single largest source of debt as a suggestion for reduction, as well out outputting the total quantity, both in test suite integration or by manually running `debt_ceiling` binary.
|
6
9
|
|
7
|
-
|
10
|
+
Travis tests are running on 1.9.3, 2.1.1 and JRuby 1.9 mode.
|
8
11
|
|
9
12
|
Current features include:
|
10
13
|
* configuring points per [RubyCritic](https://github.com/whitesmith/rubycritic) grade per file line (add FULL_ANALYSIS=true for a lengthier analysis by RubyCritic including churn and more code smells, but same grading logic, made available for use by hooks)
|
14
|
+
* Comment added explicit/manual debt assignment, via #TECH DEBT +100 or custom phrases
|
11
15
|
* Whitelisting/blacklisting files by matching path/filename
|
12
16
|
* Modifying or replacing default calculation on a per file basis
|
13
17
|
* Reporting the single greatest source of debt based on your definitions
|
14
18
|
* Reporting total debt for the git repo based on your definitions
|
15
19
|
* Adding cost for TODOs or deprecated references you specify (see .debt_ceiling.example)
|
16
|
-
* Running
|
17
|
-
* Running
|
20
|
+
* Running from a test suite to fail if debt ceiling is exceeded
|
21
|
+
* Running from a test suite to fail if debt deadline is missed (currently only supports a single deadline, could add support for multiple targets if there's interest)
|
18
22
|
|
19
|
-
To integrate in a test suite,
|
23
|
+
To integrate in a test suite, set a value for `debt_ceiling` and/or `reduction_target` and `reduction_date` in your configuration and call `DebtCeiling.calculate(root_dir)` from your test helper as an additional test. It will exit with a non-zero failure if you exceed your ceiling or miss your target, failing the test suite.
|
20
24
|
|
21
|
-
These features are largely are demonstrated/discussed in [examples/.debt_ceiling](https://github.com/bglusman/debt_ceiling/blob/master/examples/.debt_ceiling.example) which demonstrates configuring debt ceiling
|
25
|
+
These features are largely are demonstrated/discussed in [examples/.debt_ceiling.rb.example](https://github.com/bglusman/debt_ceiling/blob/master/examples/.debt_ceiling.rb.example) which demonstrates configuring debt ceiling
|
22
26
|
|
23
27
|
Additional customization is supported via two method hooks in the debt class, which debt_ceiling will load from a provided extension_file_path in the main config file, which should look like the [example file](https://github.com/bglusman/debt_ceiling/blob/master/examples/debt.rb.example)
|
24
28
|
|
25
|
-
You can configure/customize the debt calculated using a few simple commands in a .debt_ceiling file in the project's home directory
|
29
|
+
You can configure/customize the debt calculated using a few simple commands in a .debt_ceiling.rb file in the project's home directory:
|
26
30
|
|
27
31
|
```
|
28
|
-
|
29
|
-
#exceeding this will fail a test, if you run debt_ceiling binary from test suite
|
30
|
-
|
31
|
-
#exceeding this will fail after the
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
#or
|
46
|
-
#
|
32
|
+
DebtCeiling.configure do |c|
|
33
|
+
#exceeding this will fail a test, if you run debt_ceiling binary/calculate method from test suite
|
34
|
+
c.debt_ceiling = 500
|
35
|
+
#exceeding this target will fail after the reduction date (parsed by Chronic)
|
36
|
+
c.reduction_target = 100
|
37
|
+
c.reduction_date = 'Jan 1 2015'
|
38
|
+
#set the multipliers per line of code in a file with each letter grade, these are the current defaults
|
39
|
+
c.grade_points = { a: 0, b: 10, c: 20, d: 40, f: 100 }
|
40
|
+
#load custom debt calculations (see examples/debt.rb) from this path
|
41
|
+
c.extension_path = "./debt.rb"
|
42
|
+
#below two both use same mechanic, todo just assumes capital TODO as string, cost_per_todo defaults to 0
|
43
|
+
c.cost_per_todo = 50
|
44
|
+
c.deprecated_reference_pairs = { 'DEPRECATED_API' => 20}
|
45
|
+
#manually assign debt to code sections with these or with default "TECH DEBT", as a comment like #TECH DEBT +50
|
46
|
+
c.manual_callouts += ["REFACTOR THIS", "HORRIBLE HACK"]
|
47
|
+
#only count debt scores for files/folders matching these strings (converted to regexes)
|
48
|
+
c.whitelist = %w(app lib)
|
49
|
+
#or
|
50
|
+
#exclude debt scores for files/folders matching these strings (commented as mutually exclusive)
|
51
|
+
#c.blacklist = %w(config version debt_ceiling.rb)
|
52
|
+
end
|
47
53
|
```
|
48
54
|
|
49
55
|
As mentioned/linked above, additional customization is supported.
|
50
56
|
|
51
|
-
As shown in example file,
|
52
|
-
|
53
|
-
Right now it lacks all tests... feel free to open a PR!
|
54
|
-
|
55
|
-
I'll try and add test coverage where it makes sense as API matures.
|
56
|
-
|
57
|
-
## TODO: planned features/ideas
|
58
|
-
|
59
|
-
todo_cost 500 #cost per comment matching /TODO/
|
60
|
-
|
61
|
-
debt_per_reference_to deprecated_regex, 500 # help transition away from deprecated APIs
|
62
|
-
|
63
|
-
points_per_regex_match REGEX, 500 # seems like an alias for above maybe, nix?
|
57
|
+
As shown in example file, set a path for `extension_path` pointing to a file defining DebtCeiling::Debt like the one in examples directory, and define its methods for your own additional calculation per file.
|
64
58
|
|
59
|
+
### Improvement ideas/suggestsions for contributing:
|
65
60
|
|
66
|
-
|
61
|
+
* rubocop/cane integration debt for style violations
|
67
62
|
|
68
|
-
|
63
|
+
* default/custom points per reek smell detected (not currently part of rubycritic grading, despite integration)
|
69
64
|
|
70
|
-
every line over x ideal file size is y points of debt
|
65
|
+
* every line over x ideal file size is y points of debt
|
71
66
|
|
72
|
-
multipliers for important files
|
67
|
+
* multipliers for important files
|
73
68
|
|
74
|
-
|
69
|
+
* command line options to configure options per run/without a .debt_ceiling file (could be done with [ENVied](https://github.com/eval/envied) gem perhaps, or [commander](https://github.com/tj/commander) or [one of these](https://www.ruby-toolbox.com/categories/CLI_Option_Parsers)
|
75
70
|
|
76
|
-
|
71
|
+
* visualization/history of debt would be nice, but unclear how to best support... one possibility is running it against each commit in a repo, and using git-notes to add score data (and some metadata perhaps?) to store it for comparing/graphing, and for comparing branches etc. optionally configured could do this for every commit that doesn't already have a note attached, or for which the note's metadata/version is out of sync with current definitions.
|
77
72
|
|
78
|
-
* https://github.com/es-analysis/plato
|
73
|
+
* optionally include/integrate with one of these JS analysis libraries, or another if anyone had another suggestion: [plato](https://github.com/es-analysis/plato) [jsprime](https://github.com/dpnishant/jsprime) [doctorjs](https://github.com/mozilla/doctorjs)
|
79
74
|
|
80
|
-
|
75
|
+
## License
|
81
76
|
|
82
|
-
|
77
|
+
`debt_ceiling` is MIT licensed. [See the accompanying file](MIT-LICENSE.md) for
|
78
|
+
the full text.
|
data/Rakefile
CHANGED
data/bin/debt_ceiling
CHANGED
@@ -1,3 +1,3 @@
|
|
1
1
|
#!/usr/bin/env ruby
|
2
|
-
require_relative
|
3
|
-
DebtCeiling.calculate(ARGV[0] ? ARGV[0] :
|
2
|
+
require_relative '../lib/debt_ceiling'
|
3
|
+
DebtCeiling.calculate(ARGV[0] ? ARGV[0] : '.')
|
data/debt_ceiling.gemspec
CHANGED
@@ -1,32 +1,32 @@
|
|
1
1
|
# -*- encoding: utf-8 -*-
|
2
2
|
lib = File.expand_path('../lib/', __FILE__)
|
3
|
-
|
4
|
-
require
|
3
|
+
$LOAD_PATH.unshift lib unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'debt_ceiling/version'
|
5
5
|
|
6
6
|
Gem::Specification.new do |s|
|
7
|
-
s.name =
|
7
|
+
s.name = 'debt_ceiling'
|
8
8
|
s.version = DebtCeiling::VERSION
|
9
9
|
s.platform = Gem::Platform::RUBY
|
10
|
-
s.authors = [
|
11
|
-
s.email = [
|
12
|
-
s.homepage =
|
13
|
-
s.summary =
|
14
|
-
s.rubyforge_project =
|
15
|
-
s.extensions = [
|
10
|
+
s.authors = ['Brian Glusman']
|
11
|
+
s.email = ['bglusman@shutterstock.com']
|
12
|
+
s.homepage = 'https://github.com/bglusman/debt_ceiling'
|
13
|
+
s.summary = 'DebtCeiling helps you track Tech Debt'
|
14
|
+
s.rubyforge_project = 'debt_ceiling'
|
15
|
+
s.extensions = ['Rakefile']
|
16
16
|
|
17
17
|
s.description = <<-DESC
|
18
18
|
Get a grip on your technical debt
|
19
19
|
DESC
|
20
20
|
|
21
|
-
|
22
21
|
s.files = `git ls-files`.split("\n")
|
23
22
|
s.test_files = `git ls-files -- {test}/*`.split("\n")
|
24
|
-
s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
|
25
|
-
s.require_paths = [
|
23
|
+
s.executables = `git ls-files -- bin/*`.split("\n").map { |f| File.basename(f) }
|
24
|
+
s.require_paths = ['lib']
|
26
25
|
|
27
|
-
s.add_runtime_dependency
|
28
|
-
s.add_runtime_dependency
|
29
|
-
s.
|
30
|
-
s.add_development_dependency
|
31
|
-
s.add_development_dependency
|
26
|
+
s.add_runtime_dependency 'rubycritic', '~> 1.1.1'
|
27
|
+
s.add_runtime_dependency 'chronic', '~> 0.10'
|
28
|
+
s.add_runtime_dependency 'configurations', '~> 2.0.0.pre'
|
29
|
+
s.add_development_dependency 'pry', '~> 0.10'
|
30
|
+
s.add_development_dependency 'rake', '~> 10.3'
|
31
|
+
s.add_development_dependency 'rspec', '~> 3.1'
|
32
32
|
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
DebtCeiling.configure do |c|
|
2
|
+
#exceeding this will fail a test, if you run debt_ceiling binary/calculate method from test suite
|
3
|
+
c.debt_ceiling = 500
|
4
|
+
#exceeding this target will fail after the reduction date (parsed by Chronic)
|
5
|
+
c.reduction_target = 100
|
6
|
+
c.reduction_date = 'Jan 1 2015'
|
7
|
+
#set the multipliers per line of code in a file with each letter grade, these are the current defaults
|
8
|
+
c.grade_points = { a: 0, b: 10, c: 20, d: 40, f: 100 }
|
9
|
+
#load custom debt calculations (see examples/debt.rb) from this path
|
10
|
+
c.extension_path = "./debt.rb"
|
11
|
+
#below two both use same mechanic, todo just assumes capital TODO as string, cost_per_todo defaults to 0
|
12
|
+
c.cost_per_todo = 50
|
13
|
+
c.deprecated_reference_pairs = { 'DEPRECATED_API' => 20}
|
14
|
+
#manually assign debt to code sections with these or with default "TECH DEBT", as a comment like #TECH DEBT +50
|
15
|
+
c.manual_callouts += ["REFACTOR THIS", "HORRIBLE HACK"]
|
16
|
+
#only count debt scores for files/folders matching these strings (converted to regexes)
|
17
|
+
c.whitelist = %w(app lib)
|
18
|
+
#or
|
19
|
+
#exclude debt scores for files/folders matching these strings (commented as mutually exclusive)
|
20
|
+
#c.blacklist = %w(config version debt_ceiling.rb)
|
21
|
+
end
|
22
|
+
|
23
|
+
|
24
|
+
|
25
|
+
|
26
|
+
|
27
|
+
|
@@ -17,12 +17,13 @@ module DebtCeiling
|
|
17
17
|
|
18
18
|
def construct_debts(modules)
|
19
19
|
modules.map do |mod|
|
20
|
+
path = mod.path
|
20
21
|
file_attributes = OpenStruct.new
|
21
|
-
file_attributes.linecount = `wc -l #{
|
22
|
-
file_attributes.path =
|
22
|
+
file_attributes.linecount = `wc -l #{path}`.match(/\d+/)[0].to_i
|
23
|
+
file_attributes.path = path
|
23
24
|
file_attributes.analysed_module = mod
|
24
|
-
file_attributes.source_code = File.read(
|
25
|
-
|
25
|
+
file_attributes.source_code = File.read(path)
|
26
|
+
Debt.new(file_attributes)
|
26
27
|
end
|
27
28
|
end
|
28
29
|
|
@@ -30,10 +31,10 @@ module DebtCeiling
|
|
30
31
|
if ENV['FULL_ANALYSIS']
|
31
32
|
Rubycritic::Orchestrator.new.critique([path])
|
32
33
|
else
|
33
|
-
|
34
|
-
require
|
35
|
-
require
|
36
|
-
require
|
34
|
+
# temporarily use Rubycritic internals until they provide an API
|
35
|
+
require 'rubycritic/modules_initializer'
|
36
|
+
require 'rubycritic/analysers/complexity'
|
37
|
+
require 'rubycritic/analysers/smells/flay'
|
37
38
|
|
38
39
|
modules = Rubycritic::ModulesInitializer.init([path])
|
39
40
|
[Rubycritic::Analyser::Complexity, Rubycritic::Analyser::FlaySmells].each do |analyser|
|
data/lib/debt_ceiling/debt.rb
CHANGED
@@ -1,31 +1,28 @@
|
|
1
|
+
require 'forwardable'
|
1
2
|
module DebtCeiling
|
2
3
|
class Debt
|
4
|
+
extend Forwardable
|
3
5
|
DoNotWhitelistAndBlacklistSimulateneously = Class.new(StandardError)
|
4
6
|
|
5
|
-
attr_reader :file_attributes
|
7
|
+
attr_reader :file_attributes
|
8
|
+
def_delegators :file_attributes, :path, :analysed_module, :module_name, :linecount, :source_code
|
9
|
+
def_delegator :analysed_module, :rating
|
6
10
|
attr_accessor :debt_amount
|
11
|
+
def_delegator :debt_amount, :to_i
|
12
|
+
|
7
13
|
def initialize(file_attributes)
|
8
14
|
@file_attributes = file_attributes
|
9
|
-
@path = file_attributes.path
|
10
|
-
@analysed_module = file_attributes.analysed_module
|
11
|
-
@module_name = file_attributes.name
|
12
|
-
@linecount = file_attributes.linecount
|
13
|
-
@source_code = file_attributes.source_code
|
14
15
|
default_measure_debt if valid_debt?
|
15
16
|
end
|
16
17
|
|
17
18
|
def default_measure_debt
|
18
|
-
if self.respond_to?(:measure_debt)
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
cost =
|
23
|
-
|
24
|
-
|
25
|
-
0
|
26
|
-
end
|
27
|
-
letter_grade = file_attributes.analysed_module.rating.to_s.downcase
|
28
|
-
cost_per_line = DebtCeiling.public_send("#{letter_grade}_current_cost_per_line")
|
19
|
+
cost = public_send(:measure_debt) if self.respond_to?(:measure_debt)
|
20
|
+
|
21
|
+
unless cost
|
22
|
+
cost = public_send(:augment_debt) if respond_to?(:augment_debt)
|
23
|
+
cost = cost.to_i
|
24
|
+
letter_grade = rating.to_s.downcase
|
25
|
+
cost_per_line = DebtCeiling.configuration.grade_points[letter_grade.to_sym]
|
29
26
|
cost += file_attributes.linecount * cost_per_line
|
30
27
|
cost += debt_from_source_code_rules
|
31
28
|
end
|
@@ -33,43 +30,54 @@ module DebtCeiling
|
|
33
30
|
end
|
34
31
|
|
35
32
|
def debt_from_source_code_rules
|
36
|
-
|
37
|
-
DebtCeiling.
|
38
|
-
|
33
|
+
manual_callout_debt +
|
34
|
+
text_match_debt('TODO', DebtCeiling.cost_per_todo) +
|
35
|
+
DebtCeiling.deprecated_reference_pairs
|
36
|
+
.reduce(0) {|accum, (string, value)| accum + text_match_debt(string, value.to_i) }
|
39
37
|
end
|
40
38
|
|
41
39
|
def text_match_debt(string, cost)
|
42
|
-
source_code.scan(string).count * cost
|
40
|
+
source_code.scan(string).count * cost.to_i
|
41
|
+
end
|
42
|
+
|
43
|
+
def manual_callout_debt
|
44
|
+
DebtCeiling.manual_callouts.reduce(0) do |sum, callout|
|
45
|
+
sum + debt_from_callout(callout)
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
def debt_from_callout(callout)
|
50
|
+
source_code.each_line.reduce(0) do |sum, line|
|
51
|
+
match_data = line.match(Regexp.new(callout + '.*'))
|
52
|
+
string = match_data.to_s.split(callout).last
|
53
|
+
amount = string.match(/\d+/).to_s if string
|
54
|
+
sum + amount.to_i
|
55
|
+
end
|
43
56
|
end
|
44
57
|
|
45
58
|
def valid_debt?
|
46
59
|
black_empty = DebtCeiling.blacklist.empty?
|
47
60
|
white_empty = DebtCeiling.whitelist.empty?
|
48
|
-
|
61
|
+
fail DoNotWhitelistAndBlacklistSimulateneously unless black_empty || white_empty
|
49
62
|
(black_empty && white_empty) ||
|
50
63
|
(black_empty && self.class.whitelist_includes?(self)) ||
|
51
64
|
(white_empty && !self.class.blacklist_includes?(self))
|
52
65
|
end
|
53
66
|
|
54
67
|
def self.whitelist_includes?(debt)
|
55
|
-
DebtCeiling.whitelist.
|
68
|
+
DebtCeiling.whitelist.find { |filename| filename.match debt.path }
|
56
69
|
end
|
57
70
|
|
58
71
|
def self.blacklist_includes?(debt)
|
59
|
-
DebtCeiling.blacklist.
|
72
|
+
DebtCeiling.blacklist.find { |filename| filename.match debt.path }
|
60
73
|
end
|
61
74
|
|
62
75
|
def name
|
63
76
|
file_attributes.analysed_module.name || path.to_s.split('/').last
|
64
77
|
end
|
65
78
|
|
66
|
-
def to_i
|
67
|
-
debt_amount.to_i
|
68
|
-
end
|
69
|
-
|
70
79
|
def +(other)
|
71
|
-
|
80
|
+
to_i + other.to_i
|
72
81
|
end
|
73
|
-
|
74
82
|
end
|
75
|
-
end
|
83
|
+
end
|
data/lib/debt_ceiling/version.rb
CHANGED
data/lib/debt_ceiling.rb
CHANGED
@@ -1,88 +1,78 @@
|
|
1
|
+
require 'configurations'
|
2
|
+
require 'chronic'
|
1
3
|
require_relative 'debt_ceiling/accounting'
|
2
4
|
require_relative 'debt_ceiling/debt'
|
3
|
-
require 'chronic'
|
4
5
|
|
5
6
|
module DebtCeiling
|
7
|
+
include Configurations
|
8
|
+
extend Forwardable
|
6
9
|
extend self
|
7
10
|
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
11
|
+
attr_reader :debt
|
12
|
+
def_delegator :configuration, :extension_path
|
13
|
+
def_delegator :configuration, :blacklist
|
14
|
+
def_delegator :configuration, :whitelist
|
15
|
+
def_delegator :configuration, :cost_per_todo
|
16
|
+
def_delegator :configuration, :deprecated_reference_pairs
|
17
|
+
def_delegator :configuration, :manual_callouts
|
18
|
+
def_delegator :configuration, :grade_points
|
19
|
+
def_delegator :configuration, :reduction_date
|
20
|
+
def_delegator :configuration, :reduction_target
|
21
|
+
def_delegator :configuration, :debt_ceiling
|
22
|
+
|
23
|
+
configuration_defaults do |c|
|
24
|
+
c.extension_path = "#{Dir.pwd}/debt.rb"
|
25
|
+
c.blacklist = []
|
26
|
+
c.whitelist = []
|
27
|
+
c.deprecated_reference_pairs = {}
|
28
|
+
c.manual_callouts = ['TECH DEBT']
|
29
|
+
c.grade_points = { a: 0, b: 10, c: 20, d: 40, f: 100 }
|
30
|
+
end
|
31
|
+
|
32
|
+
def load_configuration
|
33
|
+
if File.exist?(Dir.pwd + '/.debt_ceiling.rb')
|
34
|
+
load(Dir.pwd + '/.debt_ceiling.rb')
|
35
|
+
elsif File.exist?(Dir.home + '/.debt_ceiling.rb')
|
36
|
+
load(Dir.home + '/.debt_ceiling.rb')
|
13
37
|
else
|
14
|
-
puts "No .debt_ceiling configuration file detected in #{Dir.pwd} or ~/, using defaults"
|
38
|
+
puts "No .debt_ceiling.rb configuration file detected in #{Dir.pwd} or ~/, using defaults"
|
15
39
|
end
|
16
40
|
|
17
|
-
extension_path
|
18
|
-
|
41
|
+
load extension_path if extension_path && File.exist?(extension_path)
|
42
|
+
@loaded = true
|
43
|
+
end
|
19
44
|
|
45
|
+
def calculate(dir = '.', opts={preconfigured: false})
|
46
|
+
load_configuration unless @loaded || opts[:preconfigured]
|
20
47
|
@debt = DebtCeiling::Accounting.calculate(dir)
|
21
48
|
evaluate
|
22
49
|
end
|
23
50
|
|
24
|
-
@extension_file_path = "#{Dir.pwd}/debt.rb"
|
25
|
-
def extension_file_path(path)
|
26
|
-
@extension_file_path = path
|
27
|
-
end
|
28
|
-
|
29
|
-
def current_extension_file_path
|
30
|
-
@extension_file_path
|
31
|
-
end
|
32
|
-
|
33
51
|
def blacklist_matching(matchers)
|
34
|
-
@blacklist = matchers.map {|matcher| Regexp.new(matcher)}
|
52
|
+
@blacklist = matchers.map { |matcher| Regexp.new(matcher) }
|
35
53
|
end
|
36
54
|
|
37
55
|
def whitelist_matching(matchers)
|
38
|
-
@whitelist = matchers.map {|matcher| Regexp.new(matcher)}
|
56
|
+
@whitelist = matchers.map { |matcher| Regexp.new(matcher) }
|
39
57
|
end
|
40
58
|
|
41
|
-
def set_debt_ceiling(value)
|
42
|
-
@ceiling_amount = value
|
43
|
-
end
|
44
|
-
|
45
|
-
def cost_per_todo(value)
|
46
|
-
@current_cost_per_todo = value.to_i
|
47
|
-
end
|
48
59
|
|
49
60
|
def debt_per_reference_to(string, value)
|
50
61
|
deprecated_reference_pairs[string] = value
|
51
62
|
end
|
52
63
|
|
53
|
-
def debt_reduction_target_and_date(target_value, date_to_parse)
|
54
|
-
@reduction_target = target_value
|
55
|
-
@reduction_date = Chronic.parse(date_to_parse)
|
56
|
-
end
|
57
64
|
|
58
65
|
def evaluate
|
59
|
-
if
|
66
|
+
if debt_ceiling && debt_ceiling <= debt
|
60
67
|
fail_test
|
61
68
|
elsif reduction_target && reduction_target <= debt &&
|
62
69
|
Time.now > reduction_date
|
63
70
|
fail_test
|
64
71
|
end
|
72
|
+
debt
|
65
73
|
end
|
66
74
|
|
67
75
|
def fail_test
|
68
76
|
Kernel.exit 1
|
69
77
|
end
|
70
|
-
|
71
|
-
attr_reader :blacklist, :whitelist, :ceiling_amount, :reduction_date, :reduction_target,
|
72
|
-
:debt, :current_cost_per_todo, :deprecated_reference_pairs
|
73
|
-
@blacklist = []
|
74
|
-
@whitelist = []
|
75
|
-
@current_cost_per_todo = 0
|
76
|
-
@deprecated_reference_pairs = {}
|
77
|
-
|
78
|
-
GRADE_MAP = {a: 0, b: 10, c: 20, d: 40, f: 100} #arbitrary default grades for now
|
79
|
-
GRADE_MAP.keys.each do |grade|
|
80
|
-
instance_variable_set "@#{grade}_cost_per_line", GRADE_MAP[grade]
|
81
|
-
define_method("#{grade}_current_cost_per_line") do
|
82
|
-
instance_variable_get "@#{grade}_cost_per_line"
|
83
|
-
end
|
84
|
-
define_method("#{grade}_cost_per_line") do |value| #def set methods, no =
|
85
|
-
instance_variable_set "@#{grade}_cost_per_line", value
|
86
|
-
end
|
87
|
-
end
|
88
78
|
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'debt_ceiling'
|
3
|
+
|
4
|
+
describe DebtCeiling do
|
5
|
+
it 'has failing exit status when debt_ceiling is exceeded' do
|
6
|
+
DebtCeiling.configure {|c| c.debt_ceiling = 0 }
|
7
|
+
expect(DebtCeiling.debt_ceiling).to eq(0)
|
8
|
+
expect { DebtCeiling.calculate('.', preconfigured: true) }.to raise_error
|
9
|
+
end
|
10
|
+
|
11
|
+
it 'has failing exit status when target debt reduction is missed' do
|
12
|
+
DebtCeiling.configure {|c| c.reduction_target =0; c.reduction_date = Time.now.to_s }
|
13
|
+
expect(DebtCeiling.debt_ceiling).to eq(nil)
|
14
|
+
expect { DebtCeiling.calculate('.', preconfigured: true) }.to raise_error
|
15
|
+
end
|
16
|
+
|
17
|
+
it 'returns quantity of total debt' do
|
18
|
+
expect(DebtCeiling.calculate('.')).to be > 5 # arbitrary non-zero amount
|
19
|
+
end
|
20
|
+
|
21
|
+
it 'adds debt for todos with specified value' do
|
22
|
+
todo_amount = 50
|
23
|
+
DebtCeiling.configure {|c| c.cost_per_todo = todo_amount }
|
24
|
+
expect(DebtCeiling.calculate('spec/support/todo_example.rb')).to be todo_amount
|
25
|
+
end
|
26
|
+
|
27
|
+
it 'allows manual debt with TECH DEBT comment' do
|
28
|
+
expect(DebtCeiling.calculate('spec/support/manual_example.rb')).to be 100 # hardcoded in example file
|
29
|
+
end
|
30
|
+
|
31
|
+
it 'allows manual debt with arbitrarily defined comment' do
|
32
|
+
DebtCeiling.configure {|c| c.manual_callouts += ['REFACTOR'] }
|
33
|
+
expect(DebtCeiling.calculate('spec/support/manual_example.rb')).to be 150 # hardcoded in example file
|
34
|
+
end
|
35
|
+
|
36
|
+
end
|
data/spec/spec_helper.rb
ADDED
metadata
CHANGED
@@ -1,87 +1,115 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: debt_ceiling
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0
|
4
|
+
version: 0.1.0
|
5
|
+
prerelease:
|
5
6
|
platform: ruby
|
6
7
|
authors:
|
7
8
|
- Brian Glusman
|
8
9
|
autorequire:
|
9
10
|
bindir: bin
|
10
11
|
cert_chain: []
|
11
|
-
date: 2014-
|
12
|
+
date: 2014-11-03 00:00:00.000000000 Z
|
12
13
|
dependencies:
|
13
14
|
- !ruby/object:Gem::Dependency
|
14
15
|
name: rubycritic
|
15
16
|
requirement: !ruby/object:Gem::Requirement
|
17
|
+
none: false
|
16
18
|
requirements:
|
17
|
-
- -
|
19
|
+
- - ~>
|
18
20
|
- !ruby/object:Gem::Version
|
19
21
|
version: 1.1.1
|
20
22
|
type: :runtime
|
21
23
|
prerelease: false
|
22
24
|
version_requirements: !ruby/object:Gem::Requirement
|
25
|
+
none: false
|
23
26
|
requirements:
|
24
|
-
- -
|
27
|
+
- - ~>
|
25
28
|
- !ruby/object:Gem::Version
|
26
29
|
version: 1.1.1
|
27
30
|
- !ruby/object:Gem::Dependency
|
28
31
|
name: chronic
|
29
32
|
requirement: !ruby/object:Gem::Requirement
|
33
|
+
none: false
|
30
34
|
requirements:
|
31
|
-
- -
|
35
|
+
- - ~>
|
32
36
|
- !ruby/object:Gem::Version
|
33
37
|
version: '0.10'
|
34
38
|
type: :runtime
|
35
39
|
prerelease: false
|
36
40
|
version_requirements: !ruby/object:Gem::Requirement
|
41
|
+
none: false
|
37
42
|
requirements:
|
38
|
-
- -
|
43
|
+
- - ~>
|
39
44
|
- !ruby/object:Gem::Version
|
40
45
|
version: '0.10'
|
46
|
+
- !ruby/object:Gem::Dependency
|
47
|
+
name: configurations
|
48
|
+
requirement: !ruby/object:Gem::Requirement
|
49
|
+
none: false
|
50
|
+
requirements:
|
51
|
+
- - ~>
|
52
|
+
- !ruby/object:Gem::Version
|
53
|
+
version: 2.0.0.pre
|
54
|
+
type: :runtime
|
55
|
+
prerelease: false
|
56
|
+
version_requirements: !ruby/object:Gem::Requirement
|
57
|
+
none: false
|
58
|
+
requirements:
|
59
|
+
- - ~>
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: 2.0.0.pre
|
41
62
|
- !ruby/object:Gem::Dependency
|
42
63
|
name: pry
|
43
64
|
requirement: !ruby/object:Gem::Requirement
|
65
|
+
none: false
|
44
66
|
requirements:
|
45
|
-
- -
|
67
|
+
- - ~>
|
46
68
|
- !ruby/object:Gem::Version
|
47
69
|
version: '0.10'
|
48
70
|
type: :development
|
49
71
|
prerelease: false
|
50
72
|
version_requirements: !ruby/object:Gem::Requirement
|
73
|
+
none: false
|
51
74
|
requirements:
|
52
|
-
- -
|
75
|
+
- - ~>
|
53
76
|
- !ruby/object:Gem::Version
|
54
77
|
version: '0.10'
|
55
78
|
- !ruby/object:Gem::Dependency
|
56
79
|
name: rake
|
57
80
|
requirement: !ruby/object:Gem::Requirement
|
81
|
+
none: false
|
58
82
|
requirements:
|
59
|
-
- -
|
83
|
+
- - ~>
|
60
84
|
- !ruby/object:Gem::Version
|
61
85
|
version: '10.3'
|
62
86
|
type: :development
|
63
87
|
prerelease: false
|
64
88
|
version_requirements: !ruby/object:Gem::Requirement
|
89
|
+
none: false
|
65
90
|
requirements:
|
66
|
-
- -
|
91
|
+
- - ~>
|
67
92
|
- !ruby/object:Gem::Version
|
68
93
|
version: '10.3'
|
69
94
|
- !ruby/object:Gem::Dependency
|
70
|
-
name:
|
95
|
+
name: rspec
|
71
96
|
requirement: !ruby/object:Gem::Requirement
|
97
|
+
none: false
|
72
98
|
requirements:
|
73
|
-
- -
|
99
|
+
- - ~>
|
74
100
|
- !ruby/object:Gem::Version
|
75
|
-
version: '
|
101
|
+
version: '3.1'
|
76
102
|
type: :development
|
77
103
|
prerelease: false
|
78
104
|
version_requirements: !ruby/object:Gem::Requirement
|
105
|
+
none: false
|
79
106
|
requirements:
|
80
|
-
- -
|
107
|
+
- - ~>
|
81
108
|
- !ruby/object:Gem::Version
|
82
|
-
version: '
|
83
|
-
description:
|
84
|
-
|
109
|
+
version: '3.1'
|
110
|
+
description: ! ' Get a grip on your technical debt
|
111
|
+
|
112
|
+
'
|
85
113
|
email:
|
86
114
|
- bglusman@shutterstock.com
|
87
115
|
executables:
|
@@ -90,43 +118,48 @@ extensions:
|
|
90
118
|
- Rakefile
|
91
119
|
extra_rdoc_files: []
|
92
120
|
files:
|
93
|
-
-
|
121
|
+
- .gitignore
|
122
|
+
- .travis.yml
|
94
123
|
- Gemfile
|
95
124
|
- LICENSE
|
125
|
+
- MIT-LICENSE.md
|
96
126
|
- README.md
|
97
127
|
- Rakefile
|
98
128
|
- bin/debt_ceiling
|
99
129
|
- debt_ceiling.gemspec
|
100
|
-
- examples/.debt_ceiling.example
|
130
|
+
- examples/.debt_ceiling.rb.example
|
101
131
|
- examples/debt.rb.example
|
102
132
|
- lib/debt_ceiling.rb
|
103
133
|
- lib/debt_ceiling/accounting.rb
|
104
134
|
- lib/debt_ceiling/debt.rb
|
105
135
|
- lib/debt_ceiling/version.rb
|
106
|
-
-
|
107
|
-
-
|
136
|
+
- spec/debt_ceiling_spec.rb
|
137
|
+
- spec/spec_helper.rb
|
138
|
+
- spec/support/manual_example.rb
|
139
|
+
- spec/support/todo_example.rb
|
108
140
|
homepage: https://github.com/bglusman/debt_ceiling
|
109
141
|
licenses: []
|
110
|
-
metadata: {}
|
111
142
|
post_install_message:
|
112
143
|
rdoc_options: []
|
113
144
|
require_paths:
|
114
145
|
- lib
|
115
146
|
required_ruby_version: !ruby/object:Gem::Requirement
|
147
|
+
none: false
|
116
148
|
requirements:
|
117
|
-
- -
|
149
|
+
- - ! '>='
|
118
150
|
- !ruby/object:Gem::Version
|
119
151
|
version: '0'
|
120
152
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
153
|
+
none: false
|
121
154
|
requirements:
|
122
|
-
- -
|
155
|
+
- - ! '>='
|
123
156
|
- !ruby/object:Gem::Version
|
124
157
|
version: '0'
|
125
158
|
requirements: []
|
126
159
|
rubyforge_project: debt_ceiling
|
127
|
-
rubygems_version:
|
160
|
+
rubygems_version: 1.8.24
|
128
161
|
signing_key:
|
129
|
-
specification_version:
|
162
|
+
specification_version: 3
|
130
163
|
summary: DebtCeiling helps you track Tech Debt
|
131
164
|
test_files: []
|
132
165
|
has_rdoc:
|
checksums.yaml
DELETED
@@ -1,7 +0,0 @@
|
|
1
|
-
---
|
2
|
-
SHA1:
|
3
|
-
metadata.gz: bea87b2ccec435c3759009586048f178a40d963f
|
4
|
-
data.tar.gz: 9538baf4d664690d4deb022db375f1a024471a2c
|
5
|
-
SHA512:
|
6
|
-
metadata.gz: 9545baeb0a2fb3f1f5bb21d3642d3e63d7aae32a9124dce6bfd11a3536a8e177f5854c37917fda715439702d18f088b97a0134c75bc9d904685d497c84fe4a9f
|
7
|
-
data.tar.gz: ed0148e021b41fa1618abf56458068a7afa1d01ee959c316987ea5f4198170a812d5a15db9ac88bb1b50295f52a7055ced1130e401139087ac1ac20db569cf47
|
@@ -1,25 +0,0 @@
|
|
1
|
-
set_debt_ceiling 500
|
2
|
-
#exceeding this will fail a test, if you run debt_ceiling binary from test suite
|
3
|
-
debt_reduction_target_and_date 100, 'Jan 1 2015'
|
4
|
-
#exceeding this will fail after the target date (parsed by Chronic)
|
5
|
-
|
6
|
-
#set the multipliers per line of code in a file with each letter grade
|
7
|
-
b_cost_per_line 10
|
8
|
-
c_cost_per_line 20
|
9
|
-
d_cost_per_line 40
|
10
|
-
f_cost_per_line 100
|
11
|
-
|
12
|
-
#load custom debt calculations (see examples/debt.rb) from this path
|
13
|
-
extension_file_path "./debt.rb"
|
14
|
-
|
15
|
-
#below two both same mechanic, todo just assumes capital TODO as string
|
16
|
-
cost_per_todo 50
|
17
|
-
|
18
|
-
debt_per_reference_to 'DEPRECATED_API', 20
|
19
|
-
|
20
|
-
|
21
|
-
#only count debt scores for file paths matching these strings (converted to regexes)
|
22
|
-
whitelist_matching %w(app lib)
|
23
|
-
#or
|
24
|
-
#exclude debt scores for files matching these strings (commented as mutually exclusive)
|
25
|
-
#blacklist_matching %w(schema.rb routes.rb)
|
data/test/debt_ceiling_test.rb
DELETED
data/test/test_helper.rb
DELETED