debtective 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 609f9971e421988a317405b19b0cb8aacc172ee246e29d2484d290f6fb6649ab
4
+ data.tar.gz: b084f3772dfb519af3a3e254fa04f576d0edcbe59961841192cd5370c2daf7ac
5
+ SHA512:
6
+ metadata.gz: 84a8a437407fba597289be78946a41094d34cca1eeb27fed496c010e0d062adba149f47832f2e9c558d92d9b0b23e13bd2fbd551168761eafeb02757c7816861
7
+ data.tar.gz: 0fb7f4326d4e3e9ee29c54117287f4eb3f9fd74083e0706b99bf2b30e68e31ef4b4f865511a80857f3b5aea49a2000f14d1f214427cdf3078115fb08b2be470a
data/MIT-LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright 2021 Edouard Piron
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,41 @@
1
+ # Debtective
2
+
3
+ Help find out todos in your application.
4
+
5
+ ## Usage
6
+
7
+ Run the task:
8
+
9
+ - `bundle exec rake debtective:todo_list`
10
+
11
+ More tasks to come!
12
+
13
+ ## Installation
14
+
15
+ Add this line to your application's Gemfile:
16
+
17
+ ```ruby
18
+ group :development do
19
+ gem 'debtective'
20
+ end
21
+ ```
22
+
23
+ And then execute:
24
+
25
+ ```bash
26
+ $ bundle
27
+ ```
28
+
29
+ Or install it yourself as:
30
+
31
+ ```bash
32
+ $ gem install debtective
33
+ ```
34
+
35
+ ## Contributing
36
+
37
+ This gem is still a work in progress. You can use GitHub issue to start a discussion.
38
+
39
+ ## License
40
+
41
+ The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
data/Rakefile ADDED
@@ -0,0 +1,12 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "bundler/gem_tasks"
4
+ require "rspec/core/rake_task"
5
+
6
+ RSpec::Core::RakeTask.new(:spec)
7
+
8
+ require "rubocop/rake_task"
9
+
10
+ RuboCop::RakeTask.new
11
+
12
+ task default: %i[spec rubocop]
@@ -0,0 +1,8 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Debtective
4
+ # Manage gem configuration
5
+ class Configuration
6
+ attr_accessor :paths
7
+ end
8
+ end
@@ -0,0 +1,69 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Debtective
4
+ # Find end of a block given its first line and the next lines
5
+ #
6
+ # With a class, a def, an if statement... or any "block statement"
7
+ #
8
+ # 0 def foo
9
+ # 1 do_this <--
10
+ # 2 end
11
+ # 3 end
12
+ #
13
+ # The end of the block is the last line before the `end` keyword (index 1)
14
+ #
15
+ # With a carriage return
16
+ #
17
+ # 0 User
18
+ # 1 .where(draft: true)
19
+ # 2 .preload(:tasks) <--
20
+ # 3 end
21
+ #
22
+ # The end of the block is the last line of the statement (index 2)
23
+ #
24
+ # With a single line (no block)
25
+ #
26
+ # 0 do_this <--
27
+ # 1 do_that
28
+ # 2 end
29
+ #
30
+ # Then the end is the first line (index 0)
31
+ #
32
+ class EndOfStatement
33
+ NON_ENDING_KEYWORDS_REGEX = /^(\s*)(else$|elsif\s|(rescue(\s|$))|ensure)/
34
+
35
+ # @param lines [Array<String>] lines of code
36
+ def initialize(lines)
37
+ @lines = lines
38
+ end
39
+
40
+ # ends of the statement
41
+ # @return [Integer]
42
+ def call
43
+ @lines[1..]&.index { end_of_statement?(_1) } || 0
44
+ end
45
+
46
+ private
47
+
48
+ # indent (number of spaces) of the first line | for memoization
49
+ # @return [Integer]
50
+ def first_line_indent
51
+ @first_line_indent ||= indent(@lines[0])
52
+ end
53
+
54
+ # return true if the line is the end of the statement
55
+ # @return [Boolean]
56
+ def end_of_statement?(line)
57
+ return false if line.match?(NON_ENDING_KEYWORDS_REGEX)
58
+
59
+ indent(line) <= first_line_indent
60
+ end
61
+
62
+ # returns the indent (number of spaces) of the line
63
+ # @param line [String]
64
+ # @return [Integer]
65
+ def indent(line)
66
+ line.match(/^(\s*).*$/)[1].length
67
+ end
68
+ end
69
+ end
@@ -0,0 +1,51 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "debtective/end_of_statement"
4
+
5
+ module Debtective
6
+ # Find the todos comments and their boundaries
7
+ class FileTodos
8
+ Result = Struct.new(:pathname, :boundaries)
9
+
10
+ BEFORE_LINE_TODO_REGEX = /^\s*#\stodo:\s/i
11
+ INLINE_TODO_REGEX = /\s*#\stodo:\s/i
12
+
13
+ # @param pathname [Pathname]
14
+ def initialize(pathname)
15
+ @pathname = pathname
16
+ end
17
+
18
+ # @return [Array<FileTodos::Result>]
19
+ def call
20
+ lines.filter_map.with_index do |line, index|
21
+ case line
22
+ when BEFORE_LINE_TODO_REGEX
23
+ Result.new(@pathname, boundaries(index))
24
+ when INLINE_TODO_REGEX
25
+ Result.new(@pathname, index..index)
26
+ end
27
+ end
28
+ end
29
+
30
+ private
31
+
32
+ # @return [Array<String>]
33
+ def lines
34
+ @lines ||= @pathname.readlines
35
+ end
36
+
37
+ # @param index [Integer]
38
+ # @return [Boundaries]
39
+ def boundaries(index)
40
+ offset = index + 2
41
+ offset..end_line(index) + offset
42
+ end
43
+
44
+ # @param index [Integer]
45
+ # @return [Integer]
46
+ def end_line(index)
47
+ next_lines = lines[(index + 1)..]
48
+ EndOfStatement.new(next_lines).call
49
+ end
50
+ end
51
+ end
@@ -0,0 +1,10 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Debtective
4
+ # Makes Rails aware of the tasks
5
+ class Railtie < Rails::Railtie
6
+ rake_tasks do
7
+ load "tasks/debtective/todo_list.rake"
8
+ end
9
+ end
10
+ end
@@ -0,0 +1,82 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "debtective/todo_list"
4
+
5
+ module Debtective
6
+ # Output information about the debt
7
+ class Report
8
+ # output todo list
9
+ def call
10
+ puts_separator
11
+ puts "todo list"
12
+ puts_separator
13
+ puts table_header
14
+ puts_separator
15
+ puts_table_content
16
+ puts_separator
17
+ puts_todos_count
18
+ puts_separator
19
+ puts_combines_lines_count
20
+ puts_separator
21
+ puts_extended_lines_count
22
+ puts_separator
23
+ end
24
+
25
+ private
26
+
27
+ # @return [TodoList::Result]
28
+ def todo_list
29
+ @todo_list ||= Debtective::TodoList.new.call
30
+ end
31
+
32
+ # @return [Array<FileTodos::Result>]
33
+ def todos
34
+ @todos ||= todo_list.todos.sort_by { _1.boundaries.size }.reverse
35
+ end
36
+
37
+ # @return [String]
38
+ def puts_separator
39
+ puts Array.new(table_header.length) { "-" }.join
40
+ end
41
+
42
+ # @return [void]
43
+ def table_header
44
+ @table_header ||= [
45
+ "pathname".ljust(120),
46
+ "boundaries".rjust(12),
47
+ "lines".rjust(12)
48
+ ].join(" | ")
49
+ end
50
+
51
+ # @return [void]
52
+ def puts_table_content
53
+ todos.each do |todo|
54
+ puts(
55
+ [
56
+ todo.pathname.to_s.ljust(120),
57
+ todo.boundaries.to_s.rjust(12),
58
+ todo.boundaries.size.to_s.rjust(12)
59
+ ].join(" | ")
60
+ )
61
+ end
62
+ end
63
+
64
+ # @return [void]
65
+ def puts_todos_count
66
+ puts "todos count"
67
+ puts todos.count
68
+ end
69
+
70
+ # @return [void]
71
+ def puts_combines_lines_count
72
+ puts "combined lines count"
73
+ puts todo_list.combined_count
74
+ end
75
+
76
+ # @return [void]
77
+ def puts_extended_lines_count
78
+ puts "extended lines count"
79
+ puts todo_list.extended_count
80
+ end
81
+ end
82
+ end
@@ -0,0 +1,65 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "debtective/file_todos"
4
+
5
+ module Debtective
6
+ # Find and investigate todo comments
7
+ class TodoList
8
+ DEFAULT_PATHS = ["./**/*"].freeze
9
+
10
+ REPORTS = %i[
11
+ combined_count
12
+ extended_count
13
+ todos
14
+ ].freeze
15
+
16
+ Result = Struct.new(*REPORTS)
17
+
18
+ # @return [TodoList::Result]
19
+ def call
20
+ Result.new(*REPORTS.map { __send__(_1) })
21
+ end
22
+
23
+ private
24
+
25
+ # @return [Array<String>]
26
+ def paths
27
+ Debtective.configuration&.paths || DEFAULT_PATHS
28
+ end
29
+
30
+ # @return [Array<Pathname>] only path to ruby files
31
+ def ruby_pathnames
32
+ paths
33
+ .flat_map { File.extname(_1) == "" ? Dir[_1] : [_1] }
34
+ .map { Pathname(_1) }
35
+ .select { _1.file? && _1.extname == ".rb" }
36
+ end
37
+
38
+ # @return [Array<FileTodos::Result>]
39
+ def todos
40
+ @todos ||=
41
+ ruby_pathnames
42
+ .flat_map { FileTodos.new(_1).call }
43
+ .select(&:any?)
44
+ end
45
+
46
+ # @return [Integer]
47
+ def extended_count
48
+ todos
49
+ .sum { _1.boundaries.size }
50
+ end
51
+
52
+ # @return [Integer]
53
+ def combined_count
54
+ todos
55
+ .group_by(&:pathname)
56
+ .values
57
+ .sum do |file_todos|
58
+ file_todos
59
+ .map { _1.boundaries.to_a }
60
+ .reduce(:|)
61
+ .length
62
+ end
63
+ end
64
+ end
65
+ end
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Debtective
4
+ VERSION = "0.2.0"
5
+ end
data/lib/debtective.rb ADDED
@@ -0,0 +1,20 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "debtective/version"
4
+ require "debtective/configuration"
5
+ require "debtective/railtie" if defined?(Rails)
6
+
7
+ # Entrypoint
8
+ module Debtective
9
+ class << self
10
+ attr_accessor :configuration
11
+
12
+ # Configures Debtective
13
+ # @return Debtective::Configuration
14
+ # @yieldparam [Proc] configuration to apply
15
+ def configure
16
+ self.configuration ||= Configuration.new
17
+ yield(configuration)
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,16 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "debtective/report"
4
+
5
+ begin
6
+ require "#{Rails.root}/config/initializers/debtective" if defined?(Rails)
7
+ rescue LoadError
8
+ nil
9
+ end
10
+
11
+ namespace :debtective do
12
+ desc "Todo List"
13
+ task :todo_list do
14
+ Debtective::Report.new.call
15
+ end
16
+ end
metadata ADDED
@@ -0,0 +1,143 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: debtective
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.2.0
5
+ platform: ruby
6
+ authors:
7
+ - Edouard Piron
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2023-02-07 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: rake
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ">="
18
+ - !ruby/object:Gem::Version
19
+ version: '0'
20
+ type: :development
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ">="
25
+ - !ruby/object:Gem::Version
26
+ version: '0'
27
+ - !ruby/object:Gem::Dependency
28
+ name: rspec
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: '0'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ">="
39
+ - !ruby/object:Gem::Version
40
+ version: '0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: rubocop
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - ">="
46
+ - !ruby/object:Gem::Version
47
+ version: '0'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - ">="
53
+ - !ruby/object:Gem::Version
54
+ version: '0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: rubocop-rspec
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - ">="
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - ">="
67
+ - !ruby/object:Gem::Version
68
+ version: '0'
69
+ - !ruby/object:Gem::Dependency
70
+ name: simplecov
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - ">="
74
+ - !ruby/object:Gem::Version
75
+ version: '0'
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - ">="
81
+ - !ruby/object:Gem::Version
82
+ version: '0'
83
+ - !ruby/object:Gem::Dependency
84
+ name: yard
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - ">="
88
+ - !ruby/object:Gem::Version
89
+ version: '0'
90
+ type: :development
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - ">="
95
+ - !ruby/object:Gem::Version
96
+ version: '0'
97
+ description: Find TODOs and compute debt size
98
+ email:
99
+ - ed.piron@gmail.com
100
+ executables: []
101
+ extensions: []
102
+ extra_rdoc_files: []
103
+ files:
104
+ - MIT-LICENSE
105
+ - README.md
106
+ - Rakefile
107
+ - lib/debtective.rb
108
+ - lib/debtective/configuration.rb
109
+ - lib/debtective/end_of_statement.rb
110
+ - lib/debtective/file_todos.rb
111
+ - lib/debtective/railtie.rb
112
+ - lib/debtective/report.rb
113
+ - lib/debtective/todo_list.rb
114
+ - lib/debtective/version.rb
115
+ - lib/tasks/debtective/todo_list.rake
116
+ homepage: https://github.com/BigBigDoudou/debtective
117
+ licenses:
118
+ - MIT
119
+ metadata:
120
+ homepage_uri: https://github.com/BigBigDoudou/debtective
121
+ source_code_uri: https://github.com/perangusta/debtective
122
+ changelog_uri: https://github.com/perangusta/debtective/CHANGELOG.md
123
+ rubygems_mfa_required: 'true'
124
+ post_install_message:
125
+ rdoc_options: []
126
+ require_paths:
127
+ - lib
128
+ required_ruby_version: !ruby/object:Gem::Requirement
129
+ requirements:
130
+ - - ">="
131
+ - !ruby/object:Gem::Version
132
+ version: '3.0'
133
+ required_rubygems_version: !ruby/object:Gem::Requirement
134
+ requirements:
135
+ - - ">="
136
+ - !ruby/object:Gem::Version
137
+ version: '0'
138
+ requirements: []
139
+ rubygems_version: 3.2.32
140
+ signing_key:
141
+ specification_version: 4
142
+ summary: Find TODOs and compute debt size
143
+ test_files: []