debt_ceiling 0.0.6 → 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
data/.travis.yml ADDED
@@ -0,0 +1,5 @@
1
+ language: ruby
2
+ rvm:
3
+ - 1.9.3
4
+ - jruby-19mode # JRuby in 1.9 mode
5
+ - 2.1.1
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
+ [![Gem Version](https://badge.fury.io/rb/debt_ceiling.svg)](http://badge.fury.io/rb/debt_ceiling)
2
+ [![Build Status](https://travis-ci.org/bglusman/debt_ceiling.svg?branch=master)](https://travis-ci.org/bglusman/debt_ceiling)
1
3
  [![Debt Ceiling Chat](https://badges.gitter.im/Join Chat.svg)](https://gitter.im/bglusman/debt_ceiling)
4
+ [![debt_ceiling API Documentation](https://www.omniref.com/ruby/gems/debt_ceiling.png)](https://www.omniref.com/ruby/gems/debt_ceiling)
2
5
 
3
6
  #DebtCeiling
4
7
 
5
- ### Work in progress, feedback and PR's appreciated
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
- Main goal is to enforce a technical debt ceiling and tech debt reduction deadlines for your Ruby project programmatically via static analysis as part of your application's test suite. Eventually perhaps will aid in visualizing tech debt as a graph or graphs (breakind down debt into various categories and sources).
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 the binary from a test suite to fail if debt ceiling is exceeded
17
- * Running the binary from a test suite to fail if debt deadline is missed
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, use `set_debt_ceiling` and/or `debt_reduction_target_and_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.
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
- set_debt_ceiling 500
29
- #exceeding this will fail a test, if you run debt_ceiling binary from test suite
30
- debt_reduction_target_and_date 100, 'Jan 1 2015'
31
- #exceeding this will fail after the target date (parsed by Chronic)
32
-
33
- #set the multipliers per line of code in a file with each letter grade
34
- b_cost_per_line 10
35
- c_cost_per_line 20
36
- d_cost_per_line 40
37
- f_cost_per_line 100
38
-
39
- #load custom debt calculations (see examples/debt.rb) from this path
40
- extension_file_path "./debt.rb"
41
-
42
- #only count debt scores for files matching these strings (converted to regexes)
43
- whitelist_matching %w(app lib)
44
-
45
- #or.... exclude debt scores for files matching these strings (obviously mutually exclusive, raises error if both present)
46
- #blacklist_matching %w(schema.rb routes.rb)
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, 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.
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
- ### Other ideas:
61
+ * rubocop/cane integration debt for style violations
67
62
 
68
- rubocop/cane integration debt for style violations
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
- 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.
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
- include one of the JS complexity/debt analysis libraries below, or another if anyone had another suggestion:
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
- * https://github.com/dpnishant/jsprime
75
+ ## License
81
76
 
82
- * https://github.com/mozilla/doctorjs
77
+ `debt_ceiling` is MIT licensed. [See the accompanying file](MIT-LICENSE.md) for
78
+ the full text.
data/Rakefile CHANGED
@@ -1,6 +1,6 @@
1
1
  #!/usr/bin/env rake
2
2
 
3
- task :default => 'test'
3
+ task default: 'test'
4
4
  task :test do
5
- sh "ruby test/debt_ceiling_test.rb"
5
+ sh 'rspec spec'
6
6
  end
data/bin/debt_ceiling CHANGED
@@ -1,3 +1,3 @@
1
1
  #!/usr/bin/env ruby
2
- require_relative "../lib/debt_ceiling"
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
- $:.unshift lib unless $:.include?(lib)
4
- require "debt_ceiling/version"
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 = "debt_ceiling"
7
+ s.name = 'debt_ceiling'
8
8
  s.version = DebtCeiling::VERSION
9
9
  s.platform = Gem::Platform::RUBY
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"]
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 = ["lib"]
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 "rubycritic", "~> 1.1.1"
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'
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 #{mod.path}`.match(/\d+/)[0].to_i
22
- file_attributes.path = mod.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(mod.path)
25
- debt_rule = Debt.new(file_attributes)
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
- #temporarily use Rubycritic internals until they provide an API
34
- require "rubycritic/modules_initializer"
35
- require "rubycritic/analysers/complexity"
36
- require "rubycritic/analysers/smells/flay"
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|
@@ -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, :path, :analysed_module, :module_name, :linecount, :source_code
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
- cost = self.public_send(:measure_debt)
20
- end
21
- if !cost
22
- cost = if self.respond_to?(:augment_debt)
23
- self.public_send(:augment_debt) || 0
24
- else
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
- text_match_debt('TODO', DebtCeiling.current_cost_per_todo) +
37
- DebtCeiling.deprecated_reference_pairs.map {|string, value|
38
- text_match_debt(string, value.to_i) }.inject(&:+).to_i
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
- raise DoNotWhitelistAndBlacklistSimulateneously if (!black_empty && !white_empty)
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.detect {|filename| filename.match debt.path }
68
+ DebtCeiling.whitelist.find { |filename| filename.match debt.path }
56
69
  end
57
70
 
58
71
  def self.blacklist_includes?(debt)
59
- DebtCeiling.blacklist.detect {|filename| filename.match debt.path }
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
- self.to_i + other.to_i
80
+ to_i + other.to_i
72
81
  end
73
-
74
82
  end
75
- end
83
+ end
@@ -1,3 +1,3 @@
1
1
  module DebtCeiling
2
- VERSION = "0.0.6"
2
+ VERSION = '0.1.0'
3
3
  end
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
- def calculate(dir=".")
9
- if File.exists?(Dir.pwd + '/.debt_ceiling')
10
- File.open(Dir.pwd + "/.debt_ceiling") {|f| DebtCeiling.module_eval(f.read)}
11
- elsif File.exists?(Dir.home + '/.debt_ceiling')
12
- File.open(Dir.home + '/.debt_ceiling') {|f| DebtCeiling.module_eval(f.read)}
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 = DebtCeiling.current_extension_file_path
18
- load extension_path if extension_path && File.exists?(extension_path)
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 ceiling_amount && ceiling_amount <= debt
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
@@ -0,0 +1,4 @@
1
+ RSpec.configure do |config|
2
+ config.before { allow($stdout).to receive(:puts) }
3
+ config.after(:each) { DebtCeiling.configure {|c| c.debt_ceiling = nil; c.reduction_target = nil; c.reduction_date = nil } }
4
+ end
@@ -0,0 +1,4 @@
1
+ class Whatever
2
+ # TECH DEBT + 100
3
+ # REFACTOR + 50
4
+ end
@@ -0,0 +1,3 @@
1
+ class Whatever
2
+ # TODO add some points for this line
3
+ end
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.6
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-10-21 00:00:00.000000000 Z
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: minitest
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: '5.4'
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: '5.4'
83
- description: |2
84
- Get a grip on your technical debt
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
- - ".gitignore"
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
- - test/debt_ceiling_test.rb
107
- - test/test_helper.rb
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: 2.2.2
160
+ rubygems_version: 1.8.24
128
161
  signing_key:
129
- specification_version: 4
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)
@@ -1,5 +0,0 @@
1
- require_relative 'test_helper'
2
-
3
- module DebtCeiling
4
-
5
- end
data/test/test_helper.rb DELETED
@@ -1,3 +0,0 @@
1
- require_relative '../lib/debt_ceiling'
2
- require 'minitest/spec'
3
- require 'minitest/autorun'