debtective 0.2.3.3 → 0.2.3.6

Sign up to get free protection for your applications and to get access to all the features.
@@ -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