debt_ceiling 0.0.1 → 0.0.2

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: bb989ae976353da509970f25093e1ae303ec8b6f
4
- data.tar.gz: a473a4ba56137d62578d03522e1519f11ab4d982
3
+ metadata.gz: 1121dd0d368a02bfad9d6f2fd21a15707c8089d8
4
+ data.tar.gz: 7dca6e9d0b291318e5e67b99a5aa96245a416c7e
5
5
  SHA512:
6
- metadata.gz: 1d4243bb0c4569c7d42d37de59102d36dc29211647f087295fe4b5807f5493c416203f54734a295a744b8b0adc65672263dedabaa2f38594ea4f67e214e6f288
7
- data.tar.gz: a4820d63a6f400e833c7015d85751aa539a58ae2c89d979d087b17eb350f397b1c2382cba9105cd5c8297b1d4b7adcb02d1e9929127c9038dad905c77cf04e74
6
+ metadata.gz: bf8aee5e06f3ec57e6215bd2e3a8d88471f2185b053cf4acb76a82ed175c6b5163f869f02dd6b30766f81cf68483fb01082dafc1ae91b9594139496c0bc34339
7
+ data.tar.gz: a3b9a6ae68acf71df05fca377df940dc5a3b85c1ed4dcaf188a681d3aeff177582c262ad3b6b6429fdf133f99f5c26ea0be16e8215196bbfe65b407188e5729d
data/README.md CHANGED
@@ -1,8 +1,76 @@
1
+ [![Debt Ceiling Chat](https://badges.gitter.im/Join Chat.svg)](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
- Current plan is to configure/customize the weight given to heuristic grade
7
- based first on a simple DSL in a .debt_ceiling file in the project's home directory, and if additional customization is desired, pass a path to
8
- `extension_file_path` command in the DSL file to a file defining DebtCeiling::Debt like the one in examples directory, and replace/augment it's methods with your own additional calculation per file.
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
@@ -21,4 +21,4 @@ end
21
21
  extension_path = DebtCeiling.current_extension_file_path
22
22
  load extension_path if File.exists?(extension_path)
23
23
 
24
- DebtCeiling.enforce(ARGV[0] ? ARGV[0] : ".")
24
+ DebtCeiling.calculate(ARGV[0] ? ARGV[0] : ".")
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/DebtCeiling"
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
- Quantify and add visibility/history to (some half-baked standin for) Technical Debt
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 "pry"
33
- s.add_development_dependency "rake"
34
- s.add_development_dependency "minitest"
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
- #TODO example gradings
2
- # debt_ceiling 15000
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
- # debt_per_reference_to blah
12
+ #load custom debt calculations (see examples/debt.rb) from this path
13
+ extension_file_path "./debt.rb"
11
14
 
12
- # points_per_regex_match REGEX, points
13
-
14
- #rubocop/cane integration debt for style violations
15
-
16
- #every line over x ideal file size is y points of debt
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)
@@ -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 augment_custom_debt
13
+ def augment_debt
14
14
  #output additional cost beyond defaults based on #file_attributes
15
15
  end
16
16
 
17
- #augment_custom_debt is only called if measure_debt is undefined or returns nil/false
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
@@ -1,31 +1,54 @@
1
1
  module DebtCeiling
2
2
  class Debt
3
- attr_reader :file_attributes
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 = file_attributes
7
- default_measure_debt
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?(:measure_file)
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?(:augment_custom_debt)
16
- self.public_send(:augment_custom_debt) || 0
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.analyzed_module.rating.to_s.downcase
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)
@@ -1,3 +1,3 @@
1
1
  module DebtCeiling
2
- VERSION = "0.0.1"
2
+ VERSION = "0.0.2"
3
3
  end
data/lib/debt_ceiling.rb CHANGED
@@ -1,35 +1,16 @@
1
- require 'ripper'
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 debt_ceiling(pattern, message=nil, options={})
11
- if pattern.kind_of?(String)
12
- rule = Rule.new(pattern, message, options)
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
- def validate_files(files)
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
- GRADES = [:a, :b, :c, :d, :f]
42
- GRADE_DEFAULTS = [0, 10, 20, 40, 100]
43
- GRADE_MAP = GRADES.zip(0..4).to_h
44
- GRADES.each do |grade|
45
- default_index = GRADE_MAP[grade]
46
- instance_variable_set "@#{grade}_cost_per_line", GRADE_DEFAULTS[default_index]
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.1
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-12 00:00:00.000000000 Z
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: pry
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: '0'
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: '0'
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: '0'
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: '0'
82
+ version: '5.4'
69
83
  description: |2
70
- Quantify and add visibility/history to (some half-baked standin for) Technical Debt
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/DebtCeiling
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