debt_ceiling 0.0.1 → 0.0.2
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 +4 -4
- data/README.md +73 -5
- data/bin/debt_ceiling +1 -1
- data/debt_ceiling.gemspec +6 -9
- data/examples/.debt_ceiling.example +12 -29
- data/examples/debt.rb.example +3 -2
- data/lib/debt_ceiling/debt.rb +31 -8
- data/lib/debt_ceiling/version.rb +1 -1
- data/lib/debt_ceiling.rb +43 -31
- metadata +31 -18
- data/lib/debt_ceiling/enforcement.rb +0 -25
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 1121dd0d368a02bfad9d6f2fd21a15707c8089d8
|
4
|
+
data.tar.gz: 7dca6e9d0b291318e5e67b99a5aa96245a416c7e
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: bf8aee5e06f3ec57e6215bd2e3a8d88471f2185b053cf4acb76a82ed175c6b5163f869f02dd6b30766f81cf68483fb01082dafc1ae91b9594139496c0bc34339
|
7
|
+
data.tar.gz: a3b9a6ae68acf71df05fca377df940dc5a3b85c1ed4dcaf188a681d3aeff177582c262ad3b6b6429fdf133f99f5c26ea0be16e8215196bbfe65b407188e5729d
|
data/README.md
CHANGED
@@ -1,8 +1,76 @@
|
|
1
|
+
[](https://gitter.im/bglusman/debt_ceiling)
|
2
|
+
|
1
3
|
#DebtCeiling
|
2
4
|
|
3
|
-
###
|
4
|
-
Work in progress, trying to use some automatic heuristic plus manual mechanisms to help visibility and tracking of technical debt.
|
5
5
|
|
6
|
-
|
7
|
-
|
8
|
-
|
6
|
+
### Work in progress, trying to use some automatic heuristic plus manual mechanisms to help visibility and tracking of technical debt.
|
7
|
+
|
8
|
+
Current features include:
|
9
|
+
* configuring points per [RubyCritic](https://github.com/whitesmith/rubycritic) grade per file line
|
10
|
+
* Whitelisting/blacklisting files by filename
|
11
|
+
* Modifying or replacing default calculation on a per file basis
|
12
|
+
* Reporting the single greatest source of debt based on your definitions
|
13
|
+
* Reporting total debt for the git repo based on your definitions
|
14
|
+
* Running the binary from a test suite to fail if debt ceiling is exceeded
|
15
|
+
* Running the binary from a test suite to fail if debt deadline is missed
|
16
|
+
|
17
|
+
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
|
18
|
+
|
19
|
+
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)
|
20
|
+
|
21
|
+
You can configure/customize the debt calculated using a few simple commands in a .debt_ceiling file in the project's home directory
|
22
|
+
|
23
|
+
```
|
24
|
+
set_debt_ceiling 500
|
25
|
+
#exceeding this will fail a test, if you run debt_ceiling binary from test suite
|
26
|
+
debt_reduction_target_and_date 100, 'Jan 1 2015'
|
27
|
+
#exceeding this will fail after the target date (parsed by Chronic)
|
28
|
+
|
29
|
+
#set the multipliers per line of code in a file with each letter grade
|
30
|
+
b_cost_per_line 10
|
31
|
+
c_cost_per_line 20
|
32
|
+
d_cost_per_line 40
|
33
|
+
f_cost_per_line 100
|
34
|
+
|
35
|
+
#load custom debt calculations (see examples/debt.rb) from this path
|
36
|
+
extension_file_path "./debt.rb"
|
37
|
+
|
38
|
+
#only count debt scores for files matching these strings (converted to regexes)
|
39
|
+
whitelist_matching %w(app lib)
|
40
|
+
|
41
|
+
#or.... exclude debt scores for files matching these strings (obviously mutually exclusive, raises error if both present)
|
42
|
+
#blacklist_matching %w(schema.rb routes.rb)
|
43
|
+
```
|
44
|
+
|
45
|
+
As mentioned/linked above, additional customization is supported.
|
46
|
+
|
47
|
+
As shown in example file, pass a path to `extension_file_path` command pointing to a file defining DebtCeiling::Debt like the one in examples directory, and define its methods for your own additional calculation per file.
|
48
|
+
|
49
|
+
Right now it lacks all tests... feel free to open a PR!
|
50
|
+
|
51
|
+
I'll try and add test coverage where it makes sense as API matures.
|
52
|
+
|
53
|
+
## TODO: planned features/ideas
|
54
|
+
|
55
|
+
todo_cost 500 #cost per comment matching /TODO/
|
56
|
+
|
57
|
+
debt_per_reference_to deprecated_regex, 500 # help transition away from deprecated APIs
|
58
|
+
|
59
|
+
points_per_regex_match REGEX, 500 # seems like an alias for above maybe, nix?
|
60
|
+
|
61
|
+
|
62
|
+
### Other ideas:
|
63
|
+
|
64
|
+
rubocop/cane integration debt for style violations
|
65
|
+
|
66
|
+
every line over x ideal file size is y points of debt
|
67
|
+
|
68
|
+
multipliers for important files
|
69
|
+
|
70
|
+
include one of the JS complexity/debt analysis libraries below, or another if anyone had another suggestion:
|
71
|
+
|
72
|
+
* https://github.com/es-analysis/plato
|
73
|
+
|
74
|
+
* https://github.com/dpnishant/jsprime
|
75
|
+
|
76
|
+
* https://github.com/mozilla/doctorjs
|
data/bin/debt_ceiling
CHANGED
data/debt_ceiling.gemspec
CHANGED
@@ -3,23 +3,19 @@ lib = File.expand_path('../lib/', __FILE__)
|
|
3
3
|
$:.unshift lib unless $:.include?(lib)
|
4
4
|
require "debt_ceiling/version"
|
5
5
|
|
6
|
-
# module ::Gem
|
7
|
-
# post_install {|gem_installer| puts 'in hook'; DebtCeiling.post_install(gem_installer) }
|
8
|
-
# end
|
9
|
-
|
10
6
|
Gem::Specification.new do |s|
|
11
7
|
s.name = "debt_ceiling"
|
12
8
|
s.version = DebtCeiling::VERSION
|
13
9
|
s.platform = Gem::Platform::RUBY
|
14
10
|
s.authors = ["Brian Glusman"]
|
15
11
|
s.email = ["bglusman@shutterstock.com"]
|
16
|
-
s.homepage = "https://github.com/bglusman/
|
12
|
+
s.homepage = "https://github.com/bglusman/debt_ceiling"
|
17
13
|
s.summary = "DebtCeiling helps you track Tech Debt"
|
18
14
|
s.rubyforge_project = "debt_ceiling"
|
19
15
|
s.extensions = ["Rakefile"]
|
20
16
|
|
21
17
|
s.description = <<-DESC
|
22
|
-
|
18
|
+
Get a grip on your technical debt
|
23
19
|
DESC
|
24
20
|
|
25
21
|
|
@@ -29,7 +25,8 @@ Gem::Specification.new do |s|
|
|
29
25
|
s.require_paths = ["lib"]
|
30
26
|
|
31
27
|
s.add_runtime_dependency "rubycritic", "~> 1.1.1"
|
32
|
-
s.add_runtime_dependency "
|
33
|
-
s.add_development_dependency "
|
34
|
-
s.add_development_dependency "
|
28
|
+
s.add_runtime_dependency "chronic", "~> 0.10"
|
29
|
+
s.add_development_dependency "pry", '~> 0.10'
|
30
|
+
s.add_development_dependency "rake", '~> 10.3'
|
31
|
+
s.add_development_dependency "minitest", '~> 5.4'
|
35
32
|
end
|
@@ -1,36 +1,19 @@
|
|
1
|
-
|
2
|
-
# debt_ceiling
|
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)
|
3
5
|
|
6
|
+
#set the multipliers per line of code in a file with each letter grade
|
4
7
|
b_cost_per_line 10
|
5
8
|
c_cost_per_line 20
|
6
9
|
d_cost_per_line 40
|
7
10
|
f_cost_per_line 100
|
8
|
-
# todo_cost 500
|
9
11
|
|
10
|
-
#
|
12
|
+
#load custom debt calculations (see examples/debt.rb) from this path
|
13
|
+
extension_file_path "./debt.rb"
|
11
14
|
|
12
|
-
#
|
13
|
-
|
14
|
-
#
|
15
|
-
|
16
|
-
#
|
17
|
-
|
18
|
-
#whitelist/blacklist API: TODO
|
19
|
-
|
20
|
-
#multipliers for important files
|
21
|
-
|
22
|
-
#ditch/augment module eval and use below or something
|
23
|
-
|
24
|
-
# class DebtRule
|
25
|
-
# def measure_file(filename, metrics)
|
26
|
-
# cost = 0
|
27
|
-
|
28
|
-
# cost += 0 if metrics.rubycritic.grade == :a
|
29
|
-
# cost += 10 if metrics.rubycritic.grade == :b
|
30
|
-
|
31
|
-
# churn = measure_churn(filename)
|
32
|
-
# cost += whatever if churn > something
|
33
|
-
# end
|
34
|
-
|
35
|
-
# def measure_churn...
|
36
|
-
# end
|
15
|
+
#only count debt scores for files matching these strings (converted to regexes)
|
16
|
+
whitelist_matching %w(app lib)
|
17
|
+
#or
|
18
|
+
#exclude debt scores for files matching these strings (commented as mutually exclusive)
|
19
|
+
#blacklist_matching %w(schema.rb routes.rb)
|
data/examples/debt.rb.example
CHANGED
@@ -10,10 +10,11 @@ module DebtCeiling
|
|
10
10
|
end
|
11
11
|
|
12
12
|
#OR do your own calcuation first, and add defaults on top of that
|
13
|
-
def
|
13
|
+
def augment_debt
|
14
14
|
#output additional cost beyond defaults based on #file_attributes
|
15
15
|
end
|
16
16
|
|
17
|
-
#
|
17
|
+
#augment_debt is only called if measure_debt is undefined or returns nil/false
|
18
|
+
#for this instance of debt (which is 1:1 mapping of Rubycritic::AnalysedModule at the moment)
|
18
19
|
end
|
19
20
|
end
|
data/lib/debt_ceiling/debt.rb
CHANGED
@@ -1,31 +1,54 @@
|
|
1
1
|
module DebtCeiling
|
2
2
|
class Debt
|
3
|
-
|
3
|
+
DoNotWhitelistAndBlacklistSimulateneously = Class.new(StandardError)
|
4
|
+
|
5
|
+
attr_reader :file_attributes, :path, :analysed_module, :module_name, :linecount
|
4
6
|
attr_accessor :debt_amount
|
5
7
|
def initialize(file_attributes)
|
6
|
-
@file_attributes
|
7
|
-
|
8
|
+
@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
|
+
default_measure_debt if valid_debt?
|
8
14
|
end
|
9
15
|
|
10
16
|
def default_measure_debt
|
11
|
-
if self.respond_to?(:
|
17
|
+
if self.respond_to?(:measure_debt)
|
12
18
|
cost = self.public_send(:measure_debt)
|
13
19
|
end
|
14
20
|
if !cost
|
15
|
-
cost = if self.respond_to?(:
|
16
|
-
self.public_send(:
|
21
|
+
cost = if self.respond_to?(:augment_debt)
|
22
|
+
self.public_send(:augment_debt) || 0
|
17
23
|
else
|
18
24
|
0
|
19
25
|
end
|
20
|
-
letter_grade = file_attributes.
|
26
|
+
letter_grade = file_attributes.analysed_module.rating.to_s.downcase
|
21
27
|
cost_per_line = DebtCeiling.public_send("#{letter_grade}_current_cost_per_line")
|
22
28
|
cost += file_attributes.linecount * cost_per_line
|
23
29
|
end
|
24
30
|
self.debt_amount = cost
|
25
31
|
end
|
26
32
|
|
33
|
+
def valid_debt?
|
34
|
+
black_empty = DebtCeiling.blacklist.empty?
|
35
|
+
white_empty = DebtCeiling.whitelist.empty?
|
36
|
+
raise DoNotWhitelistAndBlacklistSimulateneously if (!black_empty && !white_empty)
|
37
|
+
(black_empty && white_empty) ||
|
38
|
+
(black_empty && self.class.whitelist_includes?(self)) ||
|
39
|
+
(white_empty && !self.class.blacklist_includes?(self))
|
40
|
+
end
|
41
|
+
|
42
|
+
def self.whitelist_includes?(debt)
|
43
|
+
DebtCeiling.whitelist.detect {|filename| filename.match debt.path }
|
44
|
+
end
|
45
|
+
|
46
|
+
def self.blacklist_includes?(debt)
|
47
|
+
DebtCeiling.blacklist.detect {|filename| filename.match debt.path }
|
48
|
+
end
|
49
|
+
|
27
50
|
def to_i
|
28
|
-
debt_amount
|
51
|
+
debt_amount.to_i
|
29
52
|
end
|
30
53
|
|
31
54
|
def +(other)
|
data/lib/debt_ceiling/version.rb
CHANGED
data/lib/debt_ceiling.rb
CHANGED
@@ -1,35 +1,16 @@
|
|
1
|
-
|
2
|
-
require_relative 'debt_ceiling/enforcement'
|
1
|
+
require_relative 'debt_ceiling/accounting'
|
3
2
|
require_relative 'debt_ceiling/debt'
|
4
|
-
|
5
|
-
|
3
|
+
require 'chronic'
|
6
4
|
|
7
5
|
module DebtCeiling
|
8
6
|
extend self
|
9
7
|
|
10
|
-
def
|
11
|
-
|
12
|
-
|
13
|
-
DebtCeiling::Enforcement.add(rule)
|
14
|
-
else
|
15
|
-
DebtCeiling::Enforcement.add(self.send(pattern))
|
16
|
-
end
|
17
|
-
end
|
18
|
-
|
19
|
-
def enforce(dir=".")
|
20
|
-
DebtCeiling::Enforcement.process_directory(dir, :stdout)
|
8
|
+
def calculate(dir=".")
|
9
|
+
@debt = DebtCeiling::Accounting.calculate(dir)
|
10
|
+
evaluate
|
21
11
|
end
|
22
12
|
|
23
|
-
|
24
|
-
files.reduce(error: false, output:"") do |results, file|
|
25
|
-
error, output = DebtCeiling::Enforcement.process_file(file)
|
26
|
-
results[:error] ||= error
|
27
|
-
results[:output] += output
|
28
|
-
results
|
29
|
-
end
|
30
|
-
end
|
31
|
-
|
32
|
-
@extension_file_path = "#{Dir.pwd}/debt_rule.rb"
|
13
|
+
@extension_file_path = "#{Dir.pwd}/debt.rb"
|
33
14
|
def extension_file_path(path)
|
34
15
|
@extension_file_path = path
|
35
16
|
end
|
@@ -38,12 +19,43 @@ module DebtCeiling
|
|
38
19
|
@extension_file_path
|
39
20
|
end
|
40
21
|
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
22
|
+
def blacklist_matching(matchers)
|
23
|
+
@blacklist = matchers.map {|matcher| Regexp.new(matcher)}
|
24
|
+
end
|
25
|
+
|
26
|
+
def whitelist_matching(matchers)
|
27
|
+
@whitelist = matchers.map {|matcher| Regexp.new(matcher)}
|
28
|
+
end
|
29
|
+
|
30
|
+
def set_debt_ceiling(value)
|
31
|
+
@ceiling_amount = value
|
32
|
+
end
|
33
|
+
|
34
|
+
def debt_reduction_target_and_date(target_value, date_to_parse)
|
35
|
+
@reduction_target = target_value
|
36
|
+
@reduction_date = Chronic.parse(date_to_parse)
|
37
|
+
end
|
38
|
+
|
39
|
+
def evaluate
|
40
|
+
if ceiling_amount && ceiling_amount <= debt
|
41
|
+
fail_test
|
42
|
+
elsif reduction_target && reduction_target <= debt &&
|
43
|
+
Time.now > reduction_date
|
44
|
+
fail_test
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
def fail_test
|
49
|
+
Kernel.exit 1
|
50
|
+
end
|
51
|
+
|
52
|
+
attr_reader :blacklist, :whitelist, :ceiling_amount, :reduction_date, :reduction_target, :debt
|
53
|
+
@blacklist = []
|
54
|
+
@whitelist = []
|
55
|
+
|
56
|
+
GRADE_MAP = {a: 0, b: 10, c: 20, d: 40, f: 100} #arbitrary default grades for now
|
57
|
+
GRADE_MAP.keys.each do |grade|
|
58
|
+
instance_variable_set "@#{grade}_cost_per_line", GRADE_MAP[grade]
|
47
59
|
define_method("#{grade}_current_cost_per_line") do
|
48
60
|
instance_variable_get "@#{grade}_cost_per_line"
|
49
61
|
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
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.0.2
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Brian Glusman
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2014-10-
|
11
|
+
date: 2014-10-15 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: rubycritic
|
@@ -25,49 +25,63 @@ dependencies:
|
|
25
25
|
- !ruby/object:Gem::Version
|
26
26
|
version: 1.1.1
|
27
27
|
- !ruby/object:Gem::Dependency
|
28
|
-
name:
|
28
|
+
name: chronic
|
29
29
|
requirement: !ruby/object:Gem::Requirement
|
30
30
|
requirements:
|
31
|
-
- - "
|
31
|
+
- - "~>"
|
32
32
|
- !ruby/object:Gem::Version
|
33
|
-
version: '0'
|
33
|
+
version: '0.10'
|
34
34
|
type: :runtime
|
35
35
|
prerelease: false
|
36
36
|
version_requirements: !ruby/object:Gem::Requirement
|
37
37
|
requirements:
|
38
|
-
- - "
|
38
|
+
- - "~>"
|
39
39
|
- !ruby/object:Gem::Version
|
40
|
-
version: '0'
|
40
|
+
version: '0.10'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: pry
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - "~>"
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '0.10'
|
48
|
+
type: :development
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - "~>"
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '0.10'
|
41
55
|
- !ruby/object:Gem::Dependency
|
42
56
|
name: rake
|
43
57
|
requirement: !ruby/object:Gem::Requirement
|
44
58
|
requirements:
|
45
|
-
- - "
|
59
|
+
- - "~>"
|
46
60
|
- !ruby/object:Gem::Version
|
47
|
-
version: '
|
61
|
+
version: '10.3'
|
48
62
|
type: :development
|
49
63
|
prerelease: false
|
50
64
|
version_requirements: !ruby/object:Gem::Requirement
|
51
65
|
requirements:
|
52
|
-
- - "
|
66
|
+
- - "~>"
|
53
67
|
- !ruby/object:Gem::Version
|
54
|
-
version: '
|
68
|
+
version: '10.3'
|
55
69
|
- !ruby/object:Gem::Dependency
|
56
70
|
name: minitest
|
57
71
|
requirement: !ruby/object:Gem::Requirement
|
58
72
|
requirements:
|
59
|
-
- - "
|
73
|
+
- - "~>"
|
60
74
|
- !ruby/object:Gem::Version
|
61
|
-
version: '
|
75
|
+
version: '5.4'
|
62
76
|
type: :development
|
63
77
|
prerelease: false
|
64
78
|
version_requirements: !ruby/object:Gem::Requirement
|
65
79
|
requirements:
|
66
|
-
- - "
|
80
|
+
- - "~>"
|
67
81
|
- !ruby/object:Gem::Version
|
68
|
-
version: '
|
82
|
+
version: '5.4'
|
69
83
|
description: |2
|
70
|
-
|
84
|
+
Get a grip on your technical debt
|
71
85
|
email:
|
72
86
|
- bglusman@shutterstock.com
|
73
87
|
executables:
|
@@ -87,11 +101,10 @@ files:
|
|
87
101
|
- examples/debt.rb.example
|
88
102
|
- lib/debt_ceiling.rb
|
89
103
|
- lib/debt_ceiling/debt.rb
|
90
|
-
- lib/debt_ceiling/enforcement.rb
|
91
104
|
- lib/debt_ceiling/version.rb
|
92
105
|
- test/debt_ceiling_test.rb
|
93
106
|
- test/test_helper.rb
|
94
|
-
homepage: https://github.com/bglusman/
|
107
|
+
homepage: https://github.com/bglusman/debt_ceiling
|
95
108
|
licenses: []
|
96
109
|
metadata: {}
|
97
110
|
post_install_message:
|
@@ -1,25 +0,0 @@
|
|
1
|
-
require 'rubycritic'
|
2
|
-
require 'ostruct'
|
3
|
-
module DebtCeiling
|
4
|
-
class Enforcement
|
5
|
-
class << self
|
6
|
-
attr_reader :rules
|
7
|
-
def add(rule)
|
8
|
-
@rules ||= []
|
9
|
-
@rules << rule
|
10
|
-
end
|
11
|
-
|
12
|
-
def process_directory(path, output=:destructured)
|
13
|
-
modules = Rubycritic::Orchestrator.new.critique([path])
|
14
|
-
debts = modules.map do |mod|
|
15
|
-
file_attributes = OpenStruct.new
|
16
|
-
file_attributes.linecount = `wc -l #{mod.path}`.match(/\d+/)[0].to_i
|
17
|
-
file_attributes.path = mod.path
|
18
|
-
file_attributes.analyzed_module = mod
|
19
|
-
debt_rule = Debt.new(file_attributes)
|
20
|
-
end
|
21
|
-
puts "Current total tech debt: #{debts.map(&:to_i).reduce(&:+)}"
|
22
|
-
end
|
23
|
-
end
|
24
|
-
end
|
25
|
-
end
|