debtective 0.2.3.2 → 0.2.3.4
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/bin/debtective +12 -7
- data/lib/debtective/export.rb +64 -0
- data/lib/debtective/{git_commit.rb → find_commit.rb} +20 -19
- data/lib/debtective/find_end_of_statement.rb +1 -1
- data/lib/debtective/offenses/export.rb +58 -0
- data/lib/debtective/offenses/offense.rb +57 -0
- data/lib/debtective/print.rb +76 -0
- data/lib/debtective/todos/build.rb +68 -0
- data/lib/debtective/todos/export.rb +88 -0
- data/lib/debtective/todos/todo.rb +68 -0
- data/lib/debtective/version.rb +1 -1
- metadata +10 -10
- data/lib/debtective/build_todo.rb +0 -67
- data/lib/debtective/find_todos.rb +0 -41
- data/lib/debtective/output_todos.rb +0 -62
- data/lib/debtective/print_table.rb +0 -87
- data/lib/debtective/todo.rb +0 -69
- data/lib/debtective/todo_list.rb +0 -57
- data/lib/tasks/debtective/todo_list.rake +0 -29
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: '09e2ba02faff3be09a87e2babe120bb59e28f0ea509ad8cc8ef1f9a12cf55ae6'
|
4
|
+
data.tar.gz: e1c5d6197706783907a1756671928eac854d1d771eb541d58b18dba8053da003
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: f1641ef5d5031d12d7c85665a252ab168cf456297621b81c400f22c8a8017868eb3c7873eade9ae2c8b0cb21c93a85d702ff16ffd4a45b822edd0d450ca42835
|
7
|
+
data.tar.gz: f371a5a826c7daff0c6cc2a31e7e5686731f29d74b568e96174de0000bdec70c62318481ff2a21ef98ff84d279053fbbb747511f75b0e0c79c8adb160f540bcb
|
data/bin/debtective
CHANGED
@@ -2,7 +2,6 @@
|
|
2
2
|
# frozen_string_literal: true
|
3
3
|
|
4
4
|
require "debtective"
|
5
|
-
require "debtective/output_todos"
|
6
5
|
|
7
6
|
user_name =
|
8
7
|
if ARGV.include?("--me")
|
@@ -11,11 +10,17 @@ user_name =
|
|
11
10
|
ARGV[ARGV.index("--user") + 1]
|
12
11
|
end
|
13
12
|
|
14
|
-
|
15
|
-
|
13
|
+
quiet = ARGV.include?("--quiet")
|
14
|
+
|
15
|
+
case ARGV[0]
|
16
|
+
when "--todos"
|
17
|
+
require "debtective/todos/export"
|
18
|
+
Debtective::Todos::Export.new(user_name: user_name, quiet: quiet).call
|
19
|
+
when "--offenses"
|
20
|
+
require "debtective/offenses/export"
|
21
|
+
Debtective::Offenses::Export.new(user_name: user_name, quiet: quiet).call
|
22
|
+
when "--gems"
|
23
|
+
puts "Upcoming feature"
|
16
24
|
else
|
17
|
-
puts "
|
25
|
+
puts "Please pass one of this options: [--todos, --offenses, --gems]"
|
18
26
|
end
|
19
|
-
|
20
|
-
quiet = ARGV.include?("--quiet")
|
21
|
-
Debtective::OutputTodos.new(user_name, quiet: quiet).call
|
@@ -0,0 +1,64 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "json"
|
4
|
+
require "pathname"
|
5
|
+
require "debtective/print"
|
6
|
+
|
7
|
+
module Debtective
|
8
|
+
# Export elements in a JSON file
|
9
|
+
class Export
|
10
|
+
# @param user_name [String] git user email to filter
|
11
|
+
# @param quiet [boolean]
|
12
|
+
def initialize(user_name: nil, quiet: false)
|
13
|
+
@user_name = user_name
|
14
|
+
@quiet = quiet
|
15
|
+
end
|
16
|
+
|
17
|
+
# @return [void]
|
18
|
+
def call
|
19
|
+
@elements = @quiet ? find_elements : log_table
|
20
|
+
filter_elements!
|
21
|
+
log_counts unless @quiet
|
22
|
+
update_json_file
|
23
|
+
puts(FILE_PATH) unless @quiet
|
24
|
+
end
|
25
|
+
|
26
|
+
private
|
27
|
+
|
28
|
+
FILE_PATH = ""
|
29
|
+
|
30
|
+
# list of paths to search in
|
31
|
+
# @return [Array<String>]
|
32
|
+
def pathnames
|
33
|
+
(Debtective.configuration&.paths || ["./**/*"])
|
34
|
+
.flat_map { Dir[_1] }
|
35
|
+
.map { Pathname(_1) }
|
36
|
+
.select { _1.file? && _1.extname == ".rb" }
|
37
|
+
end
|
38
|
+
|
39
|
+
# select only elements commited by the given user
|
40
|
+
# @return [void]
|
41
|
+
def filter_elements!
|
42
|
+
return if @user_name.nil?
|
43
|
+
|
44
|
+
@elements.select! { _1.commit.author.name == @user_name }
|
45
|
+
end
|
46
|
+
|
47
|
+
# @return [void]
|
48
|
+
def log_counts
|
49
|
+
puts "total: #{@elements.count}"
|
50
|
+
end
|
51
|
+
|
52
|
+
# write elements into the JSON file
|
53
|
+
# @return [void]
|
54
|
+
def update_json_file
|
55
|
+
File.open(self.class::FILE_PATH, "w") do |file|
|
56
|
+
file.puts(
|
57
|
+
JSON.pretty_generate(
|
58
|
+
@elements.map(&:to_h)
|
59
|
+
)
|
60
|
+
)
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
@@ -4,21 +4,21 @@ require "git"
|
|
4
4
|
require "open3"
|
5
5
|
|
6
6
|
module Debtective
|
7
|
-
#
|
8
|
-
class
|
7
|
+
# Find the commit that introduced the given line of code
|
8
|
+
class FindCommit
|
9
9
|
Author = Struct.new(:email, :name)
|
10
10
|
Commit = Struct.new(:sha, :author, :time)
|
11
11
|
|
12
12
|
SPECIAL_CHARACTER_REGEX = /(?!\w|\s|#|:).+/
|
13
13
|
|
14
14
|
# @param pathname [Pathname] file path
|
15
|
-
# @param
|
16
|
-
def initialize(pathname
|
15
|
+
# @param code_line [String] line of code_line
|
16
|
+
def initialize(pathname:, code_line:)
|
17
17
|
@pathname = pathname
|
18
|
-
@
|
18
|
+
@code_line = code_line
|
19
19
|
end
|
20
20
|
|
21
|
-
# @return [Debtective::
|
21
|
+
# @return [Debtective::FindCommit::Commit]
|
22
22
|
def call
|
23
23
|
Commit.new(sha, author, time)
|
24
24
|
rescue Git::GitExecuteError
|
@@ -26,7 +26,7 @@ module Debtective
|
|
26
26
|
Commit.new(nil, author, nil)
|
27
27
|
end
|
28
28
|
|
29
|
-
# @return [Debtective::
|
29
|
+
# @return [Debtective::FindCommit::Author]
|
30
30
|
def author
|
31
31
|
Author.new(commit.author.email, commit.author.name)
|
32
32
|
end
|
@@ -36,17 +36,6 @@ module Debtective
|
|
36
36
|
commit.date
|
37
37
|
end
|
38
38
|
|
39
|
-
# @return [Git::Base]
|
40
|
-
def git
|
41
|
-
Git.open(".")
|
42
|
-
end
|
43
|
-
|
44
|
-
# @return [Git::Object::Commit]
|
45
|
-
def commit
|
46
|
-
git.gcommit(sha)
|
47
|
-
end
|
48
|
-
|
49
|
-
# commit sha
|
50
39
|
# @return [String]
|
51
40
|
def sha
|
52
41
|
@sha ||=
|
@@ -57,10 +46,22 @@ module Debtective
|
|
57
46
|
end
|
58
47
|
end
|
59
48
|
|
49
|
+
private
|
50
|
+
|
51
|
+
# @return [Git::Base]
|
52
|
+
def git
|
53
|
+
Git.open(".")
|
54
|
+
end
|
55
|
+
|
56
|
+
# @return [Git::Object::Commit]
|
57
|
+
def commit
|
58
|
+
git.gcommit(sha)
|
59
|
+
end
|
60
|
+
|
60
61
|
# characters " and ` can break the git command
|
61
62
|
# @return [String]
|
62
63
|
def safe_code
|
63
|
-
@
|
64
|
+
@code_line.gsub(/"/, "\\\"").gsub("`", "\\\\`")
|
64
65
|
end
|
65
66
|
end
|
66
67
|
end
|
@@ -23,7 +23,7 @@ module Debtective
|
|
23
23
|
|
24
24
|
# @param lines [Array<String>] lines of code
|
25
25
|
# @param index [Integer] index of the statement first line
|
26
|
-
def initialize(lines
|
26
|
+
def initialize(lines:, first_line_index:)
|
27
27
|
@lines = lines
|
28
28
|
@first_line_index = first_line_index
|
29
29
|
end
|
@@ -0,0 +1,58 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "debtective/export"
|
4
|
+
require "debtective/print"
|
5
|
+
require "debtective/offenses/offense"
|
6
|
+
|
7
|
+
module Debtective
|
8
|
+
module Offenses
|
9
|
+
# Export offenses in a JSON file
|
10
|
+
class Export < Debtective::Export
|
11
|
+
private
|
12
|
+
|
13
|
+
FILE_PATH = "offenses.json"
|
14
|
+
|
15
|
+
TABLE_COLUMNS = [
|
16
|
+
Debtective::Print::Column.new(
|
17
|
+
"location",
|
18
|
+
"%-80.80s",
|
19
|
+
-> { _1.location }
|
20
|
+
),
|
21
|
+
Debtective::Print::Column.new(
|
22
|
+
"author",
|
23
|
+
"%-20.20s",
|
24
|
+
-> { _1.commit.author.name || "?" }
|
25
|
+
),
|
26
|
+
Debtective::Print::Column.new(
|
27
|
+
"cop",
|
28
|
+
"%-27.27s",
|
29
|
+
-> { _1.cop || "?" }
|
30
|
+
)
|
31
|
+
].freeze
|
32
|
+
|
33
|
+
# @return [Array<Debtective::Offenses::Offense>]
|
34
|
+
def log_table
|
35
|
+
Debtective::Print.new(
|
36
|
+
columns: TABLE_COLUMNS,
|
37
|
+
track: Debtective::Offenses::Offense,
|
38
|
+
user_name: @user_name
|
39
|
+
).call { find_elements }
|
40
|
+
end
|
41
|
+
|
42
|
+
# @return [Array<Debtective::Offenses::Offense>]
|
43
|
+
def find_elements
|
44
|
+
pathnames.flat_map do |pathname|
|
45
|
+
pathname.readlines.filter_map.with_index do |line, index|
|
46
|
+
next unless line =~ /\s# rubocop:disable (.*)/
|
47
|
+
|
48
|
+
Debtective::Offenses::Offense.new(
|
49
|
+
pathname: pathname,
|
50
|
+
index: index,
|
51
|
+
cop: Regexp.last_match[1]
|
52
|
+
)
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
@@ -0,0 +1,57 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "debtective/find_commit"
|
4
|
+
|
5
|
+
module Debtective
|
6
|
+
module Offenses
|
7
|
+
# Hold offense information
|
8
|
+
class Offense
|
9
|
+
attr_accessor :pathname, :cop
|
10
|
+
|
11
|
+
# @param pathname [Pathname]
|
12
|
+
# @param cop [Array<String>]
|
13
|
+
def initialize(pathname:, index:, cop:)
|
14
|
+
@pathname = pathname
|
15
|
+
@index = index
|
16
|
+
@cop = cop
|
17
|
+
end
|
18
|
+
|
19
|
+
# location in the codebase
|
20
|
+
# @return [String]
|
21
|
+
def location
|
22
|
+
"#{@pathname}:#{@index + 1}"
|
23
|
+
end
|
24
|
+
|
25
|
+
# return commit that introduced the offense
|
26
|
+
# @return [Debtective::FindCommit::Commit]
|
27
|
+
def commit
|
28
|
+
@commit ||=
|
29
|
+
Debtective::FindCommit.new(
|
30
|
+
pathname: @pathname,
|
31
|
+
code_line: @pathname.readlines[@index]
|
32
|
+
).call
|
33
|
+
end
|
34
|
+
|
35
|
+
# @return [Integer]
|
36
|
+
def days
|
37
|
+
return if commit.time.nil?
|
38
|
+
|
39
|
+
((Time.now - commit.time) / (24 * 60 * 60)).round
|
40
|
+
end
|
41
|
+
|
42
|
+
# @return [Hash]
|
43
|
+
def to_h
|
44
|
+
{
|
45
|
+
pathname: @pathname,
|
46
|
+
location: location,
|
47
|
+
index: @index,
|
48
|
+
commit: {
|
49
|
+
sha: commit.sha,
|
50
|
+
author: commit.author.to_h,
|
51
|
+
time: commit.time
|
52
|
+
}
|
53
|
+
}
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
@@ -0,0 +1,76 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Debtective
|
4
|
+
# Print elements as a table in the stdout
|
5
|
+
class Print
|
6
|
+
Column = Struct.new(:name, :format, :lambda)
|
7
|
+
|
8
|
+
# @param columns [Array<Debtective::Print::Column>]
|
9
|
+
# @param track [Class]
|
10
|
+
# @param user_name [String] git user name to filter
|
11
|
+
def initialize(columns:, track:, user_name: nil)
|
12
|
+
@columns = columns
|
13
|
+
@track = track
|
14
|
+
@user_name = user_name
|
15
|
+
end
|
16
|
+
|
17
|
+
# @return [Array]
|
18
|
+
def call
|
19
|
+
puts separator
|
20
|
+
puts headers_row
|
21
|
+
puts separator
|
22
|
+
trace.enable
|
23
|
+
result = yield
|
24
|
+
trace.disable
|
25
|
+
puts separator
|
26
|
+
result
|
27
|
+
end
|
28
|
+
|
29
|
+
private
|
30
|
+
|
31
|
+
# use a trace to log each element as soon as it is found
|
32
|
+
# @return [Tracepoint]
|
33
|
+
def trace
|
34
|
+
TracePoint.new(:return) do |trace_point|
|
35
|
+
next unless trace_point.defined_class == @track && trace_point.method_id == :initialize
|
36
|
+
|
37
|
+
object = trace_point.self
|
38
|
+
log_object(object)
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
# @return [String]
|
43
|
+
def headers_row
|
44
|
+
@columns.map do |column|
|
45
|
+
format(column.format, column.name)
|
46
|
+
end.join(" | ")
|
47
|
+
end
|
48
|
+
|
49
|
+
# @return [String]
|
50
|
+
def object_row(object)
|
51
|
+
@columns.map do |column|
|
52
|
+
format(column.format, column.lambda.call(object))
|
53
|
+
end.join(" | ")
|
54
|
+
end
|
55
|
+
|
56
|
+
# if a user_name is given and is not the commit author
|
57
|
+
# the line is temporary and will be replaced by the next one
|
58
|
+
# @return [void]
|
59
|
+
def log_object(object)
|
60
|
+
if @user_name.nil? || @user_name == object.commit.author.name
|
61
|
+
puts object_row(object)
|
62
|
+
else
|
63
|
+
print "#{object_row(object)}\r"
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
# @return [String]
|
68
|
+
def separator
|
69
|
+
@separator ||=
|
70
|
+
begin
|
71
|
+
size = @columns.map { format(_1.format, "") }.join("---").size
|
72
|
+
"-" * size
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
@@ -0,0 +1,68 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "debtective/todos/todo"
|
4
|
+
require "debtective/find_end_of_statement"
|
5
|
+
|
6
|
+
module Debtective
|
7
|
+
module Todos
|
8
|
+
# Build a Todo object given the pathname and the comment index
|
9
|
+
class Build
|
10
|
+
BEFORE_LINE_TODO_REGEX = /^\s*#\sTODO:\s/
|
11
|
+
INLINE_TODO_REGEX = /\s*#\sTODO:\s/
|
12
|
+
COMMENT_REGEX = /^\s*#/
|
13
|
+
|
14
|
+
# @param pathname [Pathname]
|
15
|
+
# @param index [Integer]
|
16
|
+
def initialize(pathname:, index:)
|
17
|
+
@pathname = pathname
|
18
|
+
@index = index
|
19
|
+
end
|
20
|
+
|
21
|
+
# @return [Debtective::Todos::Todo]
|
22
|
+
def call
|
23
|
+
Debtective::Todos::Todo.new(
|
24
|
+
pathname: @pathname,
|
25
|
+
lines: lines,
|
26
|
+
todo_boundaries: todo_boundaries,
|
27
|
+
statement_boundaries: statement_boundaries
|
28
|
+
)
|
29
|
+
end
|
30
|
+
|
31
|
+
private
|
32
|
+
|
33
|
+
# @return [Array<String>]
|
34
|
+
def lines
|
35
|
+
@lines ||= @pathname.readlines
|
36
|
+
end
|
37
|
+
|
38
|
+
# @return [Range]
|
39
|
+
def todo_boundaries
|
40
|
+
@index..([@index, statement_boundaries.min - 1].max)
|
41
|
+
end
|
42
|
+
|
43
|
+
# range of the concerned code
|
44
|
+
# @return [Range]
|
45
|
+
def statement_boundaries
|
46
|
+
@statement_boundaries ||=
|
47
|
+
case lines[@index]
|
48
|
+
when BEFORE_LINE_TODO_REGEX
|
49
|
+
first_line_index = statement_start
|
50
|
+
last_line_index = Debtective::FindEndOfStatement.new(lines: lines, first_line_index: first_line_index).call
|
51
|
+
first_line_index..last_line_index
|
52
|
+
when INLINE_TODO_REGEX
|
53
|
+
@index..@index
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
# start index of the concerned code (i.e. below the TODO comment)
|
58
|
+
# @return [Integer]
|
59
|
+
def statement_start
|
60
|
+
lines.index.with_index do |line, i|
|
61
|
+
i > @index &&
|
62
|
+
!line.strip.empty? &&
|
63
|
+
!line.match?(COMMENT_REGEX)
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
@@ -0,0 +1,88 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "debtective/export"
|
4
|
+
require "debtective/print"
|
5
|
+
require "debtective/todos/build"
|
6
|
+
require "debtective/todos/todo"
|
7
|
+
|
8
|
+
module Debtective
|
9
|
+
module Todos
|
10
|
+
# Export todos in a JSON file
|
11
|
+
class Export < Debtective::Export
|
12
|
+
private
|
13
|
+
|
14
|
+
FILE_PATH = "todos.json"
|
15
|
+
|
16
|
+
TABLE_COLUMNS = [
|
17
|
+
Debtective::Print::Column.new(
|
18
|
+
"location",
|
19
|
+
"%-80.80s",
|
20
|
+
-> { _1.location }
|
21
|
+
),
|
22
|
+
Debtective::Print::Column.new(
|
23
|
+
"author",
|
24
|
+
"%-20.20s",
|
25
|
+
-> { _1.commit.author.name || "?" }
|
26
|
+
),
|
27
|
+
Debtective::Print::Column.new(
|
28
|
+
"days",
|
29
|
+
"%-12.12s",
|
30
|
+
-> { _1.days || "?" }
|
31
|
+
),
|
32
|
+
Debtective::Print::Column.new(
|
33
|
+
"size",
|
34
|
+
"%-12.12s",
|
35
|
+
-> { _1.size }
|
36
|
+
)
|
37
|
+
].freeze
|
38
|
+
|
39
|
+
# @return [Array<Debtective::Todos::Todo>]
|
40
|
+
def log_table
|
41
|
+
Debtective::Print.new(
|
42
|
+
columns: TABLE_COLUMNS,
|
43
|
+
track: Debtective::Todos::Todo,
|
44
|
+
user_name: @user_name
|
45
|
+
).call { find_elements }
|
46
|
+
end
|
47
|
+
|
48
|
+
# @return [Array<Debtective::Todos::Todo>]
|
49
|
+
def find_elements
|
50
|
+
pathnames.flat_map do |pathname|
|
51
|
+
pathname.readlines.filter_map.with_index do |line, index|
|
52
|
+
next unless line =~ /#\sTODO:/
|
53
|
+
|
54
|
+
Debtective::Todos::Build.new(
|
55
|
+
pathname: pathname,
|
56
|
+
index: index
|
57
|
+
).call
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
# @return [void]
|
63
|
+
def log_counts
|
64
|
+
puts "total: #{@elements.count}"
|
65
|
+
puts "extended lines count: #{extended_count}"
|
66
|
+
puts "combined lines count: #{combined_count}"
|
67
|
+
end
|
68
|
+
|
69
|
+
# @return [Integer]
|
70
|
+
def extended_count
|
71
|
+
@elements.sum(&:size)
|
72
|
+
end
|
73
|
+
|
74
|
+
# @return [Integer]
|
75
|
+
def combined_count
|
76
|
+
@elements
|
77
|
+
.group_by(&:pathname)
|
78
|
+
.values
|
79
|
+
.sum do |pathname_todos|
|
80
|
+
pathname_todos
|
81
|
+
.map { _1.statement_boundaries.to_a }
|
82
|
+
.reduce(:|)
|
83
|
+
.length
|
84
|
+
end
|
85
|
+
end
|
86
|
+
end
|
87
|
+
end
|
88
|
+
end
|
@@ -0,0 +1,68 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "debtective/find_commit"
|
4
|
+
|
5
|
+
module Debtective
|
6
|
+
module Todos
|
7
|
+
# Hold todo information
|
8
|
+
class Todo
|
9
|
+
attr_accessor :pathname, :todo_boundaries, :statement_boundaries
|
10
|
+
|
11
|
+
# @param pathname [Pathname]
|
12
|
+
# @param lines [Array<String>]
|
13
|
+
# @param todo_boundaries [Range]
|
14
|
+
# @param statement_boundaries [Range]
|
15
|
+
def initialize(pathname:, lines:, todo_boundaries:, statement_boundaries:)
|
16
|
+
@pathname = pathname
|
17
|
+
@lines = lines
|
18
|
+
@todo_boundaries = todo_boundaries
|
19
|
+
@statement_boundaries = statement_boundaries
|
20
|
+
end
|
21
|
+
|
22
|
+
# location in the codebase
|
23
|
+
# @return [String]
|
24
|
+
def location
|
25
|
+
"#{@pathname}:#{@todo_boundaries.min + 1}"
|
26
|
+
end
|
27
|
+
|
28
|
+
# size of the todo code
|
29
|
+
# @return [Integer]
|
30
|
+
def size
|
31
|
+
@statement_boundaries.size
|
32
|
+
end
|
33
|
+
|
34
|
+
# return commit that introduced the todo
|
35
|
+
# @return [Debtective::FindCommit::Commit]
|
36
|
+
def commit
|
37
|
+
@commit ||=
|
38
|
+
Debtective::FindCommit.new(
|
39
|
+
pathname: @pathname,
|
40
|
+
code_line: @lines[@todo_boundaries.min]
|
41
|
+
).call
|
42
|
+
end
|
43
|
+
|
44
|
+
# @return [Integer]
|
45
|
+
def days
|
46
|
+
return if commit.time.nil?
|
47
|
+
|
48
|
+
((Time.now - commit.time) / (24 * 60 * 60)).round
|
49
|
+
end
|
50
|
+
|
51
|
+
# @return [Hash]
|
52
|
+
def to_h
|
53
|
+
{
|
54
|
+
pathname: @pathname,
|
55
|
+
location: location,
|
56
|
+
todo_boundaries: todo_boundaries.minmax,
|
57
|
+
statement_boundaries: statement_boundaries.minmax,
|
58
|
+
size: size,
|
59
|
+
commit: {
|
60
|
+
sha: commit.sha,
|
61
|
+
author: commit.author.to_h,
|
62
|
+
time: commit.time
|
63
|
+
}
|
64
|
+
}
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
data/lib/debtective/version.rb
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: debtective
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.2.3.
|
4
|
+
version: 0.2.3.4
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Edouard Piron
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2023-02-
|
11
|
+
date: 2023-02-19 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: git
|
@@ -118,19 +118,19 @@ extra_rdoc_files: []
|
|
118
118
|
files:
|
119
119
|
- bin/debtective
|
120
120
|
- lib/debtective.rb
|
121
|
-
- lib/debtective/build_todo.rb
|
122
121
|
- lib/debtective/configuration.rb
|
122
|
+
- lib/debtective/export.rb
|
123
|
+
- lib/debtective/find_commit.rb
|
123
124
|
- lib/debtective/find_end_of_statement.rb
|
124
|
-
- lib/debtective/
|
125
|
-
- lib/debtective/
|
126
|
-
- lib/debtective/
|
127
|
-
- lib/debtective/print_table.rb
|
125
|
+
- lib/debtective/offenses/export.rb
|
126
|
+
- lib/debtective/offenses/offense.rb
|
127
|
+
- lib/debtective/print.rb
|
128
128
|
- lib/debtective/railtie.rb
|
129
129
|
- lib/debtective/stderr_helper.rb
|
130
|
-
- lib/debtective/
|
131
|
-
- lib/debtective/
|
130
|
+
- lib/debtective/todos/build.rb
|
131
|
+
- lib/debtective/todos/export.rb
|
132
|
+
- lib/debtective/todos/todo.rb
|
132
133
|
- lib/debtective/version.rb
|
133
|
-
- lib/tasks/debtective/todo_list.rake
|
134
134
|
homepage: https://github.com/BigBigDoudou/debtective
|
135
135
|
licenses:
|
136
136
|
- MIT
|
@@ -1,67 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
require "pathname"
|
4
|
-
require "debtective/todo"
|
5
|
-
require "debtective/find_end_of_statement"
|
6
|
-
|
7
|
-
module Debtective
|
8
|
-
# Find the todos comments and their boundaries
|
9
|
-
class BuildTodo
|
10
|
-
BEFORE_LINE_TODO_REGEX = /^\s*#\sTODO:\s/
|
11
|
-
INLINE_TODO_REGEX = /\s*#\sTODO:\s/
|
12
|
-
COMMENT_REGEX = /\s*#/
|
13
|
-
|
14
|
-
# @param pathname [Pathname]
|
15
|
-
# @param index [Integer]
|
16
|
-
def initialize(pathname, index)
|
17
|
-
@pathname = pathname
|
18
|
-
@index = index
|
19
|
-
end
|
20
|
-
|
21
|
-
# @return [Debtective::Todo]
|
22
|
-
def call
|
23
|
-
Todo.new(
|
24
|
-
@pathname,
|
25
|
-
lines,
|
26
|
-
todo_boundaries,
|
27
|
-
statement_boundaries
|
28
|
-
)
|
29
|
-
end
|
30
|
-
|
31
|
-
private
|
32
|
-
|
33
|
-
# @return [Array<String>]
|
34
|
-
def lines
|
35
|
-
@lines ||= @pathname.readlines
|
36
|
-
end
|
37
|
-
|
38
|
-
# @return [Range]
|
39
|
-
def todo_boundaries
|
40
|
-
@index..([@index, statement_boundaries.min - 1].max)
|
41
|
-
end
|
42
|
-
|
43
|
-
# range of the concerned code
|
44
|
-
# @return [Range]
|
45
|
-
def statement_boundaries
|
46
|
-
@statement_boundaries ||=
|
47
|
-
case lines[@index]
|
48
|
-
when BEFORE_LINE_TODO_REGEX
|
49
|
-
first_line_index = statement_start
|
50
|
-
last_line_index = FindEndOfStatement.new(lines, first_line_index).call
|
51
|
-
first_line_index..last_line_index
|
52
|
-
when INLINE_TODO_REGEX
|
53
|
-
@index..@index
|
54
|
-
end
|
55
|
-
end
|
56
|
-
|
57
|
-
# start index of the concerned code (i.e. below the TODO comment)
|
58
|
-
# @return [Integer]
|
59
|
-
def statement_start
|
60
|
-
lines.index.with_index do |line, i|
|
61
|
-
i > @index &&
|
62
|
-
!line.strip.empty? &&
|
63
|
-
!line.match?(COMMENT_REGEX)
|
64
|
-
end
|
65
|
-
end
|
66
|
-
end
|
67
|
-
end
|
@@ -1,41 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
require "pathname"
|
4
|
-
require "debtective/build_todo"
|
5
|
-
|
6
|
-
module Debtective
|
7
|
-
# Find and investigate todo comments and return a list of todos
|
8
|
-
class FindTodos
|
9
|
-
TODO_REGEX = /#\sTODO:/
|
10
|
-
|
11
|
-
# @param paths [Array<String>]
|
12
|
-
def initialize(paths)
|
13
|
-
@paths = paths
|
14
|
-
end
|
15
|
-
|
16
|
-
# @return [Array<Debtective::Todo>]
|
17
|
-
def call
|
18
|
-
ruby_pathnames.flat_map { pathname_todos(_1) }
|
19
|
-
end
|
20
|
-
|
21
|
-
private
|
22
|
-
|
23
|
-
# @return [Array<Pathname>] only pathes to ruby files
|
24
|
-
def ruby_pathnames
|
25
|
-
@paths
|
26
|
-
.flat_map { Dir[_1] }
|
27
|
-
.map { Pathname(_1) }
|
28
|
-
.select { _1.file? && _1.extname == ".rb" }
|
29
|
-
end
|
30
|
-
|
31
|
-
# return todos in the pathname
|
32
|
-
# @return [Array<Debtective::Todo>]
|
33
|
-
def pathname_todos(pathname)
|
34
|
-
pathname.readlines.filter_map.with_index do |line, index|
|
35
|
-
next unless line.match?(TODO_REGEX)
|
36
|
-
|
37
|
-
Todo.build(pathname, index)
|
38
|
-
end
|
39
|
-
end
|
40
|
-
end
|
41
|
-
end
|
@@ -1,62 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
require "json"
|
4
|
-
require "debtective/print_table"
|
5
|
-
require "pry"
|
6
|
-
|
7
|
-
module Debtective
|
8
|
-
# Generate todolist
|
9
|
-
class OutputTodos
|
10
|
-
FILE_PATH = "todos.json"
|
11
|
-
|
12
|
-
# @param user_name [String] git user email to filter
|
13
|
-
def initialize(user_name = nil, quiet: false)
|
14
|
-
@user_name = user_name
|
15
|
-
@quiet = quiet
|
16
|
-
end
|
17
|
-
|
18
|
-
# @return [void]
|
19
|
-
def call
|
20
|
-
@todo_list = log_table
|
21
|
-
@todo_list ||= Debtective::TodoList.new(Debtective.configuration&.paths || ["./**/*"])
|
22
|
-
filter_todo_list!
|
23
|
-
log_counts
|
24
|
-
update_json_file
|
25
|
-
puts FILE_PATH
|
26
|
-
end
|
27
|
-
|
28
|
-
private
|
29
|
-
|
30
|
-
# @return [void]
|
31
|
-
def log_table
|
32
|
-
return if @quiet
|
33
|
-
|
34
|
-
Debtective::PrintTable.new(@user_name).call
|
35
|
-
end
|
36
|
-
|
37
|
-
# @return [void]
|
38
|
-
def filter_todo_list!
|
39
|
-
!@user_name.nil? && @todo_list.todos.select! { _1.commit.author.name == @user_name }
|
40
|
-
end
|
41
|
-
|
42
|
-
# @return [void]
|
43
|
-
def log_counts
|
44
|
-
@return if @quiet
|
45
|
-
|
46
|
-
puts "total: #{@todo_list.todos.count}"
|
47
|
-
puts "combined lines count: #{@todo_list.combined_count}"
|
48
|
-
puts "extended lines count: #{@todo_list.extended_count}"
|
49
|
-
end
|
50
|
-
|
51
|
-
# @return [void]
|
52
|
-
def update_json_file
|
53
|
-
File.open(FILE_PATH, "w") do |file|
|
54
|
-
file.puts(
|
55
|
-
JSON.pretty_generate(
|
56
|
-
@todo_list.todos.map(&:to_h)
|
57
|
-
)
|
58
|
-
)
|
59
|
-
end
|
60
|
-
end
|
61
|
-
end
|
62
|
-
end
|
@@ -1,87 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
require "debtective/todo_list"
|
4
|
-
|
5
|
-
module Debtective
|
6
|
-
# Print todos as a table in the stdout
|
7
|
-
class PrintTable
|
8
|
-
# @param user_name [String] git user email to filter
|
9
|
-
def initialize(user_name = nil)
|
10
|
-
@user_name = user_name
|
11
|
-
end
|
12
|
-
|
13
|
-
# @return [Debtective::TodoList]
|
14
|
-
def call
|
15
|
-
log_table_headers
|
16
|
-
log_table_rows
|
17
|
-
todo_list
|
18
|
-
end
|
19
|
-
|
20
|
-
private
|
21
|
-
|
22
|
-
# @return [void]
|
23
|
-
def log_table_headers
|
24
|
-
puts separator
|
25
|
-
puts table_row("location", "author", "days", "size")
|
26
|
-
puts separator
|
27
|
-
end
|
28
|
-
|
29
|
-
# @return [void]
|
30
|
-
def log_table_rows
|
31
|
-
trace.enable
|
32
|
-
todo_list.todos
|
33
|
-
trace.disable
|
34
|
-
puts separator
|
35
|
-
end
|
36
|
-
|
37
|
-
# use a trace to log each todo as soon as it is found
|
38
|
-
# @return [Tracepoint]
|
39
|
-
def trace
|
40
|
-
TracePoint.new(:return) do |trace_point|
|
41
|
-
next unless trace_point.defined_class == Debtective::BuildTodo && trace_point.method_id == :call
|
42
|
-
|
43
|
-
todo = trace_point.return_value
|
44
|
-
log_todo(todo)
|
45
|
-
end
|
46
|
-
end
|
47
|
-
|
48
|
-
# if a user_name is given and is not the commit author
|
49
|
-
# the line is temporary and will be replaced by the next one
|
50
|
-
# @return [void]
|
51
|
-
def log_todo(todo)
|
52
|
-
row = table_row(
|
53
|
-
todo.location,
|
54
|
-
todo.commit.author.name || "?",
|
55
|
-
todo.days || "?",
|
56
|
-
todo.size
|
57
|
-
)
|
58
|
-
if @user_name.nil? || @user_name == todo.commit.author.name
|
59
|
-
puts row
|
60
|
-
else
|
61
|
-
print "#{row}\r"
|
62
|
-
end
|
63
|
-
end
|
64
|
-
|
65
|
-
# @return [Debtective::Todo]
|
66
|
-
def todo_list
|
67
|
-
@todo_list ||= Debtective::TodoList.new(
|
68
|
-
Debtective.configuration&.paths || ["./**/*"]
|
69
|
-
)
|
70
|
-
end
|
71
|
-
|
72
|
-
# @return [String]
|
73
|
-
def table_row(location, author, days, size)
|
74
|
-
[
|
75
|
-
format("%-80.80s", location),
|
76
|
-
format("%-20.20s", author),
|
77
|
-
format("%-12.12s", days),
|
78
|
-
format("%-12.12s", size)
|
79
|
-
].join(" | ")
|
80
|
-
end
|
81
|
-
|
82
|
-
# @return [String]
|
83
|
-
def separator
|
84
|
-
@separator ||= Array.new(table_row(nil, nil, nil, nil).size) { "-" }.join
|
85
|
-
end
|
86
|
-
end
|
87
|
-
end
|
data/lib/debtective/todo.rb
DELETED
@@ -1,69 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
require "debtective/git_commit"
|
4
|
-
|
5
|
-
module Debtective
|
6
|
-
# Hold todo information
|
7
|
-
class Todo
|
8
|
-
class << self
|
9
|
-
# @return [Debtective::Todo]
|
10
|
-
def build(pathname, index)
|
11
|
-
BuildTodo.new(pathname, index).call
|
12
|
-
end
|
13
|
-
end
|
14
|
-
|
15
|
-
attr_accessor :pathname, :todo_boundaries, :statement_boundaries
|
16
|
-
|
17
|
-
# @param pathname [Pathname]
|
18
|
-
# @param lines [Array<String>]
|
19
|
-
# @param todo_boundaries [Range]
|
20
|
-
# @param statement_boundaries [Range]
|
21
|
-
def initialize(pathname, lines, todo_boundaries, statement_boundaries)
|
22
|
-
@pathname = pathname
|
23
|
-
@lines = lines
|
24
|
-
@todo_boundaries = todo_boundaries
|
25
|
-
@statement_boundaries = statement_boundaries
|
26
|
-
end
|
27
|
-
|
28
|
-
# location in the codebase
|
29
|
-
# @return [String]
|
30
|
-
def location
|
31
|
-
"#{@pathname}:#{@todo_boundaries.min + 1}"
|
32
|
-
end
|
33
|
-
|
34
|
-
# size of the todo code
|
35
|
-
# @return [Integer]
|
36
|
-
def size
|
37
|
-
@statement_boundaries.size
|
38
|
-
end
|
39
|
-
|
40
|
-
# return commit that introduced the todo
|
41
|
-
# @return [Git::Object::Commit]
|
42
|
-
def commit
|
43
|
-
@commit ||= Debtective::GitCommit.new(@pathname, @lines[@todo_boundaries.min]).call
|
44
|
-
end
|
45
|
-
|
46
|
-
# @return [Integer]
|
47
|
-
def days
|
48
|
-
return if commit.time.nil?
|
49
|
-
|
50
|
-
((Time.now - commit.time) / (24 * 60 * 60)).round
|
51
|
-
end
|
52
|
-
|
53
|
-
# @return [Hash]
|
54
|
-
def to_h
|
55
|
-
{
|
56
|
-
pathname: pathname,
|
57
|
-
location: location,
|
58
|
-
todo_boundaries: todo_boundaries.minmax,
|
59
|
-
statement_boundaries: statement_boundaries.minmax,
|
60
|
-
size: size,
|
61
|
-
commit: {
|
62
|
-
sha: commit.sha,
|
63
|
-
author: commit.author.to_h,
|
64
|
-
time: commit.time
|
65
|
-
}
|
66
|
-
}
|
67
|
-
end
|
68
|
-
end
|
69
|
-
end
|
data/lib/debtective/todo_list.rb
DELETED
@@ -1,57 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
require "debtective/find_todos"
|
4
|
-
|
5
|
-
module Debtective
|
6
|
-
# Information about the todos in the codebase
|
7
|
-
class TodoList
|
8
|
-
Author = Struct.new(:email, :name, :todos)
|
9
|
-
|
10
|
-
# @param paths [Array<String>]
|
11
|
-
def initialize(paths)
|
12
|
-
@paths = paths
|
13
|
-
end
|
14
|
-
|
15
|
-
# @return [Array<Debtective::Todo>]
|
16
|
-
def todos
|
17
|
-
@todos ||= Debtective::FindTodos.new(@paths).call
|
18
|
-
end
|
19
|
-
|
20
|
-
# @return [Array<TodoList::Author>]
|
21
|
-
def authors
|
22
|
-
todos
|
23
|
-
.map { [_1.commit.author.email, _1.commit.author.name] }
|
24
|
-
.uniq
|
25
|
-
.map { author(_1) }
|
26
|
-
end
|
27
|
-
|
28
|
-
# @return [Integer]
|
29
|
-
def extended_count
|
30
|
-
todos.sum(&:size)
|
31
|
-
end
|
32
|
-
|
33
|
-
# @return [Integer]
|
34
|
-
def combined_count
|
35
|
-
todos
|
36
|
-
.group_by(&:pathname)
|
37
|
-
.values
|
38
|
-
.sum do |pathname_todos|
|
39
|
-
pathname_todos
|
40
|
-
.map { _1.statement_boundaries.to_a }
|
41
|
-
.reduce(:|)
|
42
|
-
.length
|
43
|
-
end
|
44
|
-
end
|
45
|
-
|
46
|
-
private
|
47
|
-
|
48
|
-
# @param email_and_name [Array<String>]
|
49
|
-
# @return [TodoList::Author]
|
50
|
-
def author(email_and_name)
|
51
|
-
Author.new(
|
52
|
-
*email_and_name,
|
53
|
-
todos.select { email_and_name == [_1.commit.author.email, _1.commit.author.name] }
|
54
|
-
)
|
55
|
-
end
|
56
|
-
end
|
57
|
-
end
|
@@ -1,29 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
require "debtective/output_todos"
|
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
|
-
user_name =
|
15
|
-
if ARGV.include?("--me")
|
16
|
-
`git config user.name`.strip
|
17
|
-
elsif ARGV.include?("--user")
|
18
|
-
ARGV[ARGV.index("--user") + 1]
|
19
|
-
end
|
20
|
-
|
21
|
-
if user_name
|
22
|
-
puts "Searching TODOs from #{user_name}..."
|
23
|
-
else
|
24
|
-
puts "Searching TODOs..."
|
25
|
-
end
|
26
|
-
|
27
|
-
Debtective::OutputTodos.new(user_name, quiet: ARGV.include?("--quiet")).call
|
28
|
-
end
|
29
|
-
end
|