debtective 0.2.3.3 → 0.2.3.6

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.
@@ -0,0 +1,83 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "export"
4
+
5
+ module Debtective
6
+ module Comments
7
+ # Print elements as a table in the stdout and return the block result
8
+ class Print
9
+ Column = Struct.new(:name, :format, :lambda)
10
+
11
+ COLUMNS = [
12
+ Column.new(
13
+ "location",
14
+ "%-100.100s",
15
+ -> { _1.location }
16
+ ),
17
+ Column.new(
18
+ "type",
19
+ "%-7.7s",
20
+ -> { _1.type }
21
+ ),
22
+ Column.new(
23
+ "author",
24
+ "%-20.20s",
25
+ -> { _1.commit.author.name || "?" }
26
+ )
27
+ ].freeze
28
+
29
+ # @param user_name [String] git user name to filter with
30
+ def initialize(user_name: nil)
31
+ @user_name = user_name
32
+ end
33
+
34
+ # @return [Object] yielded bloc result
35
+ def call
36
+ puts separator
37
+ puts headers_row
38
+ puts separator
39
+ trace.enable
40
+ result = yield
41
+ trace.disable
42
+ puts separator
43
+ result
44
+ end
45
+
46
+ private
47
+
48
+ # Use a trace to log each element as soon as it is exported
49
+ # @return [Tracepoint]
50
+ def trace
51
+ TracePoint.new(:return) do |trace_point|
52
+ next unless trace_point.defined_class == Export && trace_point.method_id == :export_comment
53
+
54
+ log_comment(trace_point.return_value)
55
+ end
56
+ end
57
+
58
+ def headers_row
59
+ COLUMNS.map { format(_1.format, _1.name) }.join(" | ")
60
+ end
61
+
62
+ def comment_row(comment)
63
+ COLUMNS.map { format(_1.format, _1.lambda.call(comment)) }.join(" | ")
64
+ end
65
+
66
+ # If a user_name is given and is not the commit author
67
+ # the line is temporary and will be replaced by the next one
68
+ def log_comment(comment)
69
+ return if comment.nil?
70
+
71
+ if @user_name.nil? || @user_name == comment.commit.author.name
72
+ puts comment_row(comment)
73
+ else
74
+ print "#{comment_row(comment)}\r"
75
+ end
76
+ end
77
+
78
+ def separator
79
+ @separator ||= "-" * COLUMNS.map { format(_1.format, "") }.join("---").size
80
+ end
81
+ end
82
+ end
83
+ end
@@ -3,6 +3,7 @@
3
3
  module Debtective
4
4
  # Silence the $stderr
5
5
  module StderrHelper
6
+ # Prevent logs in stderr
6
7
  # @return void
7
8
  def suppress_stderr
8
9
  original_stderr = $stderr.clone
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Debtective
4
- VERSION = "0.2.3.3"
4
+ VERSION = "0.2.3.6"
5
5
  end
data/lib/debtective.rb CHANGED
@@ -1,20 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require "debtective/version"
4
- require "debtective/configuration"
5
- require "debtective/railtie" if defined?(Rails)
6
4
 
7
5
  # 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
6
+ module Debtective; end
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.3
4
+ version: 0.2.3.6
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-18 00:00:00.000000000 Z
11
+ date: 2023-03-27 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: git
@@ -24,6 +24,20 @@ dependencies:
24
24
  - - ">="
25
25
  - !ruby/object:Gem::Version
26
26
  version: '0'
27
+ - !ruby/object:Gem::Dependency
28
+ name: parser
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: '0'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ">="
39
+ - !ruby/object:Gem::Version
40
+ version: '0'
27
41
  - !ruby/object:Gem::Dependency
28
42
  name: rake
29
43
  requirement: !ruby/object:Gem::Requirement
@@ -118,17 +132,22 @@ extra_rdoc_files: []
118
132
  files:
119
133
  - bin/debtective
120
134
  - lib/debtective.rb
121
- - lib/debtective/configuration.rb
122
- - lib/debtective/find_commit.rb
123
- - lib/debtective/find_end_of_statement.rb
124
- - lib/debtective/railtie.rb
135
+ - lib/debtective/command.rb
136
+ - lib/debtective/comments/build_comment.rb
137
+ - lib/debtective/comments/command.rb
138
+ - lib/debtective/comments/comment/base.rb
139
+ - lib/debtective/comments/comment/fixme.rb
140
+ - lib/debtective/comments/comment/magic.rb
141
+ - lib/debtective/comments/comment/note.rb
142
+ - lib/debtective/comments/comment/offense.rb
143
+ - lib/debtective/comments/comment/shebang.rb
144
+ - lib/debtective/comments/comment/todo.rb
145
+ - lib/debtective/comments/comment/yard.rb
146
+ - lib/debtective/comments/export.rb
147
+ - lib/debtective/comments/find_commit.rb
148
+ - lib/debtective/comments/find_end_of_statement.rb
149
+ - lib/debtective/comments/print.rb
125
150
  - lib/debtective/stderr_helper.rb
126
- - lib/debtective/todos/build.rb
127
- - lib/debtective/todos/find.rb
128
- - lib/debtective/todos/list.rb
129
- - lib/debtective/todos/output.rb
130
- - lib/debtective/todos/print_table.rb
131
- - lib/debtective/todos/todo.rb
132
151
  - lib/debtective/version.rb
133
152
  homepage: https://github.com/BigBigDoudou/debtective
134
153
  licenses:
@@ -1,8 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Debtective
4
- # Manage gem configuration
5
- class Configuration
6
- attr_accessor :paths
7
- end
8
- end
@@ -1,66 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require "git"
4
- require "open3"
5
-
6
- module Debtective
7
- # find the commit that introduced a line of code
8
- class FindCommit
9
- Author = Struct.new(:email, :name)
10
- Commit = Struct.new(:sha, :author, :time)
11
-
12
- SPECIAL_CHARACTER_REGEX = /(?!\w|\s|#|:).+/
13
-
14
- # @param pathname [Pathname] file path
15
- # @param code [String] line of code
16
- def initialize(pathname, code)
17
- @pathname = pathname
18
- @code = code
19
- end
20
-
21
- # @return [Debtective::FindCommit::Commit]
22
- def call
23
- Commit.new(sha, author, time)
24
- rescue Git::GitExecuteError
25
- author = Author.new(nil, nil)
26
- Commit.new(nil, author, nil)
27
- end
28
-
29
- # @return [Debtective::FindCommit::Author]
30
- def author
31
- Author.new(commit.author.email, commit.author.name)
32
- end
33
-
34
- # @return [Time]
35
- def time
36
- commit.date
37
- end
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
- # @return [String]
51
- def sha
52
- @sha ||=
53
- begin
54
- cmd = "git log -S \"#{safe_code}\" #{@pathname}"
55
- stdout, _stderr, _status = ::Open3.capture3(cmd)
56
- stdout[/commit (\w{40})\n/, 1]
57
- end
58
- end
59
-
60
- # characters " and ` can break the git command
61
- # @return [String]
62
- def safe_code
63
- @code.gsub(/"/, "\\\"").gsub("`", "\\\\`")
64
- end
65
- end
66
- end
@@ -1,72 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require "debtective/stderr_helper"
4
-
5
- module Debtective
6
- # Find the index of the line ending a statement
7
- #
8
- # @example
9
- # FindEndOfStatement.new(
10
- # [
11
- # "class User",
12
- # " def example",
13
- # " x + y",
14
- # " end"
15
- # "end"
16
- # ],
17
- # 1
18
- # ).call
19
- # => 3
20
- #
21
- class FindEndOfStatement
22
- include StderrHelper
23
-
24
- # @param lines [Array<String>] lines of code
25
- # @param index [Integer] index of the statement first line
26
- def initialize(lines, first_line_index)
27
- @lines = lines
28
- @first_line_index = first_line_index
29
- end
30
-
31
- # index of the line ending the statement
32
- # @return [Integer]
33
- # @note use suppress_stderr to prevent error outputs
34
- # from RubyVM::InstructionSequence.compile
35
- def call
36
- suppress_stderr { last_line_index }
37
- end
38
-
39
- private
40
-
41
- # recursively find index of the line ending the statement
42
- # @param index [Integer] used for recursion
43
- # @return[Integer, void]
44
- # @note it is possible that no line ends the statement
45
- # especially if first line is not the start of a statement
46
- def last_line_index(index = @first_line_index)
47
- return @first_line_index if index >= @lines.size
48
- return index if !chained?(index) && statement?(index)
49
-
50
- last_line_index(index + 1)
51
- end
52
-
53
- # check if the code from first index to given index is a statement
54
- # e.i. can be parsed by a ruby parser (using whitequark/parser)
55
- # @param index [Integer]
56
- # @return boolean
57
- def statement?(index)
58
- code = @lines[@first_line_index..index].join("\n")
59
- RubyVM::InstructionSequence.compile(code)
60
- rescue SyntaxError
61
- false
62
- end
63
-
64
- # check if current line is chained
65
- # e.i. next line start with a .
66
- # @param index [Integer]
67
- # @return boolean
68
- def chained?(index)
69
- @lines[index + 1]&.match?(/^(\s*)\./)
70
- end
71
- end
72
- end
@@ -1,10 +0,0 @@
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
@@ -1,69 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require "pathname"
4
- require "debtective/todos/todo"
5
- require "debtective/find_end_of_statement"
6
-
7
- module Debtective
8
- module Todos
9
- # Find the todos comments and their boundaries
10
- class Build
11
- BEFORE_LINE_TODO_REGEX = /^\s*#\sTODO:\s/
12
- INLINE_TODO_REGEX = /\s*#\sTODO:\s/
13
- COMMENT_REGEX = /\s*#/
14
-
15
- # @param pathname [Pathname]
16
- # @param index [Integer]
17
- def initialize(pathname, index)
18
- @pathname = pathname
19
- @index = index
20
- end
21
-
22
- # @return [Debtective::Todos::Todo]
23
- def call
24
- Debtective::Todos::Todo.new(
25
- @pathname,
26
- lines,
27
- todo_boundaries,
28
- statement_boundaries
29
- )
30
- end
31
-
32
- private
33
-
34
- # @return [Array<String>]
35
- def lines
36
- @lines ||= @pathname.readlines
37
- end
38
-
39
- # @return [Range]
40
- def todo_boundaries
41
- @index..([@index, statement_boundaries.min - 1].max)
42
- end
43
-
44
- # range of the concerned code
45
- # @return [Range]
46
- def statement_boundaries
47
- @statement_boundaries ||=
48
- case lines[@index]
49
- when BEFORE_LINE_TODO_REGEX
50
- first_line_index = statement_start
51
- last_line_index = Debtective::FindEndOfStatement.new(lines, first_line_index).call
52
- first_line_index..last_line_index
53
- when INLINE_TODO_REGEX
54
- @index..@index
55
- end
56
- end
57
-
58
- # start index of the concerned code (i.e. below the TODO comment)
59
- # @return [Integer]
60
- def statement_start
61
- lines.index.with_index do |line, i|
62
- i > @index &&
63
- !line.strip.empty? &&
64
- !line.match?(COMMENT_REGEX)
65
- end
66
- end
67
- end
68
- end
69
- end
@@ -1,43 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require "pathname"
4
- require "debtective/todos/build"
5
-
6
- module Debtective
7
- module Todos
8
- # Find and investigate todo comments and return a list of todos
9
- class Find
10
- TODO_REGEX = /#\sTODO:/
11
-
12
- # @param paths [Array<String>]
13
- def initialize(paths)
14
- @paths = paths
15
- end
16
-
17
- # @return [Array<Debtective::Todos::Todo>]
18
- def call
19
- ruby_pathnames.flat_map { pathname_todos(_1) }
20
- end
21
-
22
- private
23
-
24
- # @return [Array<Pathname>] only pathes to ruby files
25
- def ruby_pathnames
26
- @paths
27
- .flat_map { Dir[_1] }
28
- .map { Pathname(_1) }
29
- .select { _1.file? && _1.extname == ".rb" }
30
- end
31
-
32
- # return todos in the pathname
33
- # @return [Array<Debtective::Todos::Todo>]
34
- def pathname_todos(pathname)
35
- pathname.readlines.filter_map.with_index do |line, index|
36
- next unless line.match?(TODO_REGEX)
37
-
38
- Debtective::Todos::Todo.build(pathname, index)
39
- end
40
- end
41
- end
42
- end
43
- end
@@ -1,59 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require "debtective/todos/find"
4
-
5
- module Debtective
6
- module Todos
7
- # Information about the todos in the codebase
8
- class List
9
- Author = Struct.new(:email, :name, :todos)
10
-
11
- # @param paths [Array<String>]
12
- def initialize(paths)
13
- @paths = paths
14
- end
15
-
16
- # @return [Array<Debtective::Todos::Todo>]
17
- def todos
18
- @todos ||= Debtective::Todos::Find.new(@paths).call
19
- end
20
-
21
- # @return [Array<Debtective::Todos::List::Author>]
22
- def authors
23
- todos
24
- .map { [_1.commit.author.email, _1.commit.author.name] }
25
- .uniq
26
- .map { author(_1) }
27
- end
28
-
29
- # @return [Integer]
30
- def extended_count
31
- todos.sum(&:size)
32
- end
33
-
34
- # @return [Integer]
35
- def combined_count
36
- todos
37
- .group_by(&:pathname)
38
- .values
39
- .sum do |pathname_todos|
40
- pathname_todos
41
- .map { _1.statement_boundaries.to_a }
42
- .reduce(:|)
43
- .length
44
- end
45
- end
46
-
47
- private
48
-
49
- # @param email_and_name [Array<String>]
50
- # @return [Debtective::Todos::List::Author]
51
- def author(email_and_name)
52
- Author.new(
53
- *email_and_name,
54
- todos.select { email_and_name == [_1.commit.author.email, _1.commit.author.name] }
55
- )
56
- end
57
- end
58
- end
59
- end
@@ -1,65 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require "json"
4
- require "debtective/todos/print_table"
5
-
6
- module Debtective
7
- module Todos
8
- # Generate todolist
9
- class Output
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
- @list = log_table
21
- @list ||= Debtective::Todos::List.new(Debtective.configuration&.paths || ["./**/*"])
22
- filter_list!
23
- log_counts
24
- update_json_file
25
- return if @quiet
26
-
27
- puts FILE_PATH
28
- end
29
-
30
- private
31
-
32
- # @return [void]
33
- def log_table
34
- return if @quiet
35
-
36
- Debtective::Todos::PrintTable.new(@user_name).call
37
- end
38
-
39
- # @return [void]
40
- def filter_list!
41
- !@user_name.nil? && @list.todos.select! { _1.commit.author.name == @user_name }
42
- end
43
-
44
- # @return [void]
45
- def log_counts
46
- return if @quiet
47
-
48
- puts "total: #{@list.todos.count}"
49
- puts "combined lines count: #{@list.combined_count}"
50
- puts "extended lines count: #{@list.extended_count}"
51
- end
52
-
53
- # @return [void]
54
- def update_json_file
55
- File.open(FILE_PATH, "w") do |file|
56
- file.puts(
57
- JSON.pretty_generate(
58
- @list.todos.map(&:to_h)
59
- )
60
- )
61
- end
62
- end
63
- end
64
- end
65
- end
@@ -1,89 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require "debtective/todos/list"
4
-
5
- module Debtective
6
- module Todos
7
- # Print todos as a table in the stdout
8
- class PrintTable
9
- # @param user_name [String] git user email to filter
10
- def initialize(user_name = nil)
11
- @user_name = user_name
12
- end
13
-
14
- # @return [Debtective::Todos::List]
15
- def call
16
- log_table_headers
17
- log_table_rows
18
- list
19
- end
20
-
21
- private
22
-
23
- # @return [void]
24
- def log_table_headers
25
- puts separator
26
- puts table_row("location", "author", "days", "size")
27
- puts separator
28
- end
29
-
30
- # @return [void]
31
- def log_table_rows
32
- trace.enable
33
- list.todos
34
- trace.disable
35
- puts separator
36
- end
37
-
38
- # use a trace to log each todo as soon as it is found
39
- # @return [Tracepoint]
40
- def trace
41
- TracePoint.new(:return) do |trace_point|
42
- next unless trace_point.defined_class == Debtective::Todos::Build && trace_point.method_id == :call
43
-
44
- todo = trace_point.return_value
45
- log_todo(todo)
46
- end
47
- end
48
-
49
- # if a user_name is given and is not the commit author
50
- # the line is temporary and will be replaced by the next one
51
- # @return [void]
52
- def log_todo(todo)
53
- row = table_row(
54
- todo.location,
55
- todo.commit.author.name || "?",
56
- todo.days || "?",
57
- todo.size
58
- )
59
- if @user_name.nil? || @user_name == todo.commit.author.name
60
- puts row
61
- else
62
- print "#{row}\r"
63
- end
64
- end
65
-
66
- # @return [Debtective::Todos::Todo]
67
- def list
68
- @list ||= Debtective::Todos::List.new(
69
- Debtective.configuration&.paths || ["./**/*"]
70
- )
71
- end
72
-
73
- # @return [String]
74
- def table_row(location, author, days, size)
75
- [
76
- format("%-80.80s", location),
77
- format("%-20.20s", author),
78
- format("%-12.12s", days),
79
- format("%-12.12s", size)
80
- ].join(" | ")
81
- end
82
-
83
- # @return [String]
84
- def separator
85
- @separator ||= Array.new(table_row(nil, nil, nil, nil).size) { "-" }.join
86
- end
87
- end
88
- end
89
- end