pdd 0.20.4 → 0.20.8
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/.gitignore +5 -4
- data/.overcommit.yml +96 -0
- data/.pdd +15 -1
- data/.rubocop.yml +3 -2
- data/.rultor.yml +14 -15
- data/.simplecov +6 -6
- data/.travis.yml +2 -2
- data/Gemfile +1 -1
- data/LICENSE.txt +1 -1
- data/README.md +72 -67
- data/Rakefile +7 -1
- data/assets/puzzles.xsd +1 -1
- data/assets/puzzles.xsl +1 -1
- data/bin/pdd +30 -19
- data/features/catches_broken_puzzles.feature +1 -16
- data/features/cli.feature +1 -1
- data/features/html_output.feature +1 -1
- data/features/rake.feature +21 -0
- data/features/step_definitions/steps.rb +11 -4
- data/features/support/env.rb +2 -1
- data/features/uses_config.feature +1 -1
- data/lib/pdd/puzzle.rb +1 -1
- data/lib/pdd/rake_task.rb +39 -0
- data/lib/pdd/rule/duplicates.rb +2 -1
- data/lib/pdd/rule/estimates.rb +1 -1
- data/lib/pdd/rule/roles.rb +2 -1
- data/lib/pdd/rule/text.rb +2 -1
- data/lib/pdd/source.rb +59 -47
- data/lib/pdd/sources.rb +34 -13
- data/lib/pdd/version.rb +3 -3
- data/lib/pdd.rb +20 -15
- data/pdd.gemspec +13 -10
- data/test/test__helper.rb +15 -2
- data/test/test_duplicates.rb +2 -2
- data/test/test_estimates.rb +2 -2
- data/test/test_pdd.rb +4 -2
- data/test/test_rake_task.rb +18 -0
- data/test/test_roles.rb +2 -2
- data/test/test_source.rb +154 -47
- data/test/test_source_todo.rb +36 -9
- data/test/test_sources.rb +18 -2
- data/test/test_text.rb +2 -2
- data/utils/glob.rb +67 -0
- metadata +51 -17
data/bin/pdd
CHANGED
@@ -1,5 +1,5 @@
|
|
1
1
|
#!/usr/bin/env ruby
|
2
|
-
# Copyright (c) 2014-
|
2
|
+
# Copyright (c) 2014-2021 Yegor Bugayenko
|
3
3
|
#
|
4
4
|
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
5
5
|
# of this software and associated documentation files (the 'Software'), to deal
|
@@ -19,8 +19,11 @@
|
|
19
19
|
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
20
20
|
# SOFTWARE.
|
21
21
|
|
22
|
-
|
22
|
+
$stdout.sync = true
|
23
23
|
|
24
|
+
require 'shellwords'
|
25
|
+
require 'English'
|
26
|
+
require 'find'
|
24
27
|
require 'slop'
|
25
28
|
require 'nokogiri'
|
26
29
|
require 'rainbow'
|
@@ -46,6 +49,8 @@ begin
|
|
46
49
|
o.bool '-v', '--verbose', 'Enable verbose mode (a lot of logging)'
|
47
50
|
o.bool '-q', '--quiet', 'Enable quiet mode (almost no logging)'
|
48
51
|
o.bool '--skip-gitignore', 'Don\'t look into .gitignore for excludes'
|
52
|
+
o.bool '--skip-errors', 'Suppress error as warning and skip badly
|
53
|
+
formatted puzzles'
|
49
54
|
o.bool '-i', '--version', 'Show current version' do
|
50
55
|
puts PDD::VERSION
|
51
56
|
exit
|
@@ -53,21 +58,23 @@ begin
|
|
53
58
|
o.string '-s', '--source', 'Source directory to parse ("." by default)'
|
54
59
|
o.string '-f', '--file', 'File to save XML into'
|
55
60
|
o.array '-e', '--exclude', 'Glob pattern to exclude, e.g. "**/*.jpg"',
|
56
|
-
|
61
|
+
default: []
|
62
|
+
o.array '-n', '--include', 'Glob pattern to include, e.g. "**/*.jpg"',
|
63
|
+
default: []
|
57
64
|
o.string '-t', '--format', 'Format of the report (xml|html)'
|
58
65
|
o.array(
|
59
66
|
'-r', '--rule', 'Rule to apply (can be used many times)',
|
60
67
|
delimiter: ';'
|
61
68
|
)
|
62
69
|
end
|
63
|
-
rescue Slop::Error =>
|
64
|
-
raise StandardError, "#{
|
70
|
+
rescue Slop::Error => e
|
71
|
+
raise StandardError, "#{e.message}, try --help"
|
65
72
|
end
|
66
73
|
|
67
74
|
if opts.help?
|
68
75
|
puts opts
|
69
76
|
puts "This is our README to learn more: \
|
70
|
-
https://github.com/
|
77
|
+
https://github.com/cqfn/pdd/blob/master/README.md"
|
71
78
|
exit
|
72
79
|
end
|
73
80
|
|
@@ -75,13 +82,17 @@ https://github.com/yegor256/pdd/blob/master/README.md"
|
|
75
82
|
raise '-f is mandatory when using -v, try --help for more information'
|
76
83
|
end
|
77
84
|
|
78
|
-
if opts['skip-gitignore']
|
79
|
-
|
85
|
+
if opts['skip-gitignore'] && File.exist?('.gitignore')
|
86
|
+
cfg = File.new('.gitignore')
|
87
|
+
body = File.read(cfg)
|
88
|
+
extra = body.split(/\s+/).map(&:strip)
|
89
|
+
opts['skip-gitignore'] = extra
|
90
|
+
puts "Found #{body.split(/\n/).length} lines in #{File.absolute_path(cfg)}"
|
80
91
|
end
|
81
92
|
|
82
93
|
Encoding.default_external = Encoding::UTF_8
|
83
94
|
Encoding.default_internal = Encoding::UTF_8
|
84
|
-
file = opts.file? ? File.new(opts[:file], 'w') :
|
95
|
+
file = opts.file? ? File.new(opts[:file], 'w') : $stdout
|
85
96
|
output = PDD::Base.new(opts).xml
|
86
97
|
if opts[:format]
|
87
98
|
if opts[:format] == 'html'
|
@@ -95,21 +106,21 @@ https://github.com/yegor256/pdd/blob/master/README.md"
|
|
95
106
|
end
|
96
107
|
end
|
97
108
|
file << output
|
98
|
-
rescue SystemExit =>
|
99
|
-
puts
|
100
|
-
PDD.log.info "Exit code is #{
|
101
|
-
exit(
|
102
|
-
rescue PDD::Error =>
|
103
|
-
puts "#{Rainbow('ERROR').red}: #{
|
109
|
+
rescue SystemExit => e
|
110
|
+
puts e.message unless e.success?
|
111
|
+
PDD.log.info "Exit code is #{e.status}"
|
112
|
+
exit(e.status)
|
113
|
+
rescue PDD::Error => e
|
114
|
+
puts "#{Rainbow('ERROR').red}: #{e.message}
|
104
115
|
If you can't understand the cause of this issue or you don't know \
|
105
116
|
how to fix it, please submit a GitHub issue, we will try to help you: \
|
106
|
-
https://github.com/
|
117
|
+
https://github.com/cqfn/pdd/issues. This tool is still in its beta \
|
107
118
|
version and we will appreciate your feedback. Here is where you can find \
|
108
|
-
more documentation: https://github.com/
|
119
|
+
more documentation: https://github.com/cqfn/pdd/blob/master/README.md."
|
109
120
|
PDD.log.info 'Exit code is 1'
|
110
121
|
exit(1)
|
111
|
-
rescue StandardError =>
|
112
|
-
puts "#{Rainbow('ERROR').red} (#{
|
122
|
+
rescue StandardError => e
|
123
|
+
puts "#{Rainbow('ERROR').red} (#{e.class.name}): #{e.message}"
|
113
124
|
PDD.log.info 'Exit code is 255'
|
114
125
|
exit(255)
|
115
126
|
end
|
@@ -21,22 +21,7 @@ Feature: Catches Broken Puzzles
|
|
21
21
|
}
|
22
22
|
"""
|
23
23
|
When I run pdd it fails with "Space expected"
|
24
|
-
When I run pdd it fails with "
|
25
|
-
|
26
|
-
Scenario: Throwing exception on another broken puzzle
|
27
|
-
Given I have a "Sample.java" file with content:
|
28
|
-
"""
|
29
|
-
public class Main {
|
30
|
-
/**
|
31
|
-
* @todo #13 This puzzle has an incorrect format
|
32
|
-
* because its second line starts with too many spaces
|
33
|
-
*/
|
34
|
-
public void main(String[] args) {
|
35
|
-
// later
|
36
|
-
}
|
37
|
-
}
|
38
|
-
"""
|
39
|
-
When I run pdd it fails with "Too many leading spaces"
|
24
|
+
When I run pdd it fails with "Sample.java:6"
|
40
25
|
|
41
26
|
Scenario: Throwing exception on yet another broken puzzle
|
42
27
|
Given I have a "Sample.java" file with content:
|
data/features/cli.feature
CHANGED
@@ -26,7 +26,7 @@ Feature: Command Line Processing
|
|
26
26
|
"""
|
27
27
|
When I run bin/pdd with "-v -s . -f out.xml"
|
28
28
|
Then Exit code is zero
|
29
|
-
And Stdout contains "Reading ."
|
29
|
+
And Stdout contains "Reading from root dir ."
|
30
30
|
And XML file "out.xml" matches "/puzzles[count(puzzle)=1]"
|
31
31
|
And XML file "out.xml" matches "//puzzle[starts-with(body,'Привет, Let')]"
|
32
32
|
|
@@ -0,0 +1,21 @@
|
|
1
|
+
Feature: Rake Task
|
2
|
+
As a source code writer I want to be able to
|
3
|
+
run PDD from Rakefile
|
4
|
+
Scenario: PDD can be used in Rakefile
|
5
|
+
Given It is Unix
|
6
|
+
And I have a "Rakefile" file with content:
|
7
|
+
"""
|
8
|
+
require 'pdd/rake_task'
|
9
|
+
PDD::RakeTask.new(:pdd) do |task|
|
10
|
+
task.includes = ['a.txt']
|
11
|
+
end
|
12
|
+
"""
|
13
|
+
And I have a "a.txt" file with content:
|
14
|
+
"""
|
15
|
+
\x40todo #55 hello!
|
16
|
+
"""
|
17
|
+
|
18
|
+
When I run bash with "rake pdd"
|
19
|
+
Then Exit code is zero
|
20
|
+
|
21
|
+
|
@@ -1,4 +1,4 @@
|
|
1
|
-
# Copyright (c) 2014-
|
1
|
+
# Copyright (c) 2014-2021 Yegor Bugayenko
|
2
2
|
#
|
3
3
|
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
4
4
|
# of this software and associated documentation files (the 'Software'), to deal
|
@@ -64,9 +64,9 @@ When(/^I run pdd it fails with "([^"]*)"$/) do |txt|
|
|
64
64
|
begin
|
65
65
|
PDD::Base.new(@opts).xml
|
66
66
|
passed = true
|
67
|
-
rescue PDD::Error =>
|
68
|
-
unless
|
69
|
-
raise "PDD failed but exception doesn't contain \"#{txt}\": #{
|
67
|
+
rescue PDD::Error => e
|
68
|
+
unless e.message.include?(txt)
|
69
|
+
raise "PDD failed but exception doesn't contain \"#{txt}\": #{e.message}"
|
70
70
|
end
|
71
71
|
end
|
72
72
|
raise "PDD didn't fail" if passed
|
@@ -90,6 +90,7 @@ end
|
|
90
90
|
|
91
91
|
Then(/^XML file "([^"]+)" matches "([^"]+)"$/) do |file, xpath|
|
92
92
|
raise "File #{file} doesn't exit" unless File.exist?(file)
|
93
|
+
|
93
94
|
xml = Nokogiri::XML.parse(File.read(file))
|
94
95
|
xml.remove_namespaces!
|
95
96
|
if xml.xpath(xpath).empty?
|
@@ -111,6 +112,12 @@ When(/^I run bash with$/) do |text|
|
|
111
112
|
@exitstatus = $CHILD_STATUS.exitstatus
|
112
113
|
end
|
113
114
|
|
115
|
+
When(/^I run bash with "([^"]*)"$/) do |text|
|
116
|
+
FileUtils.copy_entry(@cwd, File.join(@dir, 'pdd'))
|
117
|
+
@stdout = `#{text}`
|
118
|
+
@exitstatus = $CHILD_STATUS.exitstatus
|
119
|
+
end
|
120
|
+
|
114
121
|
Given(/^It is Unix$/) do
|
115
122
|
pending if Gem.win_platform?
|
116
123
|
end
|
data/features/support/env.rb
CHANGED
@@ -1,4 +1,4 @@
|
|
1
|
-
# Copyright (c) 2014-
|
1
|
+
# Copyright (c) 2014-2021 Yegor Bugayenko
|
2
2
|
#
|
3
3
|
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
4
4
|
# of this software and associated documentation files (the 'Software'), to deal
|
@@ -20,3 +20,4 @@
|
|
20
20
|
|
21
21
|
require 'simplecov'
|
22
22
|
require_relative '../../lib/pdd'
|
23
|
+
require 'aruba/cucumber'
|
data/lib/pdd/puzzle.rb
CHANGED
@@ -0,0 +1,39 @@
|
|
1
|
+
require 'rake'
|
2
|
+
require 'rake/tasklib'
|
3
|
+
require 'minitest/autorun'
|
4
|
+
require 'nokogiri'
|
5
|
+
require 'tmpdir'
|
6
|
+
require 'slop'
|
7
|
+
require 'pdd'
|
8
|
+
|
9
|
+
# PDD Rake task
|
10
|
+
module PDD
|
11
|
+
# Rake task
|
12
|
+
class RakeTask < Rake::TaskLib
|
13
|
+
attr_accessor :name, :fail_on_error, :includes, :license, :quiet
|
14
|
+
|
15
|
+
def initialize(*args, &task_block)
|
16
|
+
@name = args.shift || :pdd
|
17
|
+
@includes = []
|
18
|
+
@excludes = []
|
19
|
+
@license = nil
|
20
|
+
@quiet = false
|
21
|
+
desc 'Run PDD' unless ::Rake.application.last_description
|
22
|
+
task(name, *args) do |_, task_args|
|
23
|
+
RakeFileUtils.send(:verbose, true) do
|
24
|
+
yield(*[self, task_args].slice(0, task_block.arity)) if block_given?
|
25
|
+
run
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
private
|
31
|
+
|
32
|
+
def run
|
33
|
+
# @todo #125:30m need to implement this method.
|
34
|
+
# For now, it's just a task,
|
35
|
+
# that prints a simple Running pdd... message to user
|
36
|
+
puts 'Running pdd...' unless @quiet
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
data/lib/pdd/rule/duplicates.rb
CHANGED
@@ -1,4 +1,4 @@
|
|
1
|
-
# Copyright (c) 2014-
|
1
|
+
# Copyright (c) 2014-2021 Yegor Bugayenko
|
2
2
|
#
|
3
3
|
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
4
4
|
# of this software and associated documentation files (the 'Software'), to deal
|
@@ -35,6 +35,7 @@ module PDD
|
|
35
35
|
.group_by { |p| p.xpath('body/text()').to_s }
|
36
36
|
.map do |_, puzzles|
|
37
37
|
next nil if puzzles.count <= @max
|
38
|
+
|
38
39
|
"there are #{puzzles.count} duplicate(s) of the same puzzle: " +
|
39
40
|
puzzles.map do |p|
|
40
41
|
"#{p.xpath('file/text()')}:#{p.xpath('lines/text()')}"
|
data/lib/pdd/rule/estimates.rb
CHANGED
data/lib/pdd/rule/roles.rb
CHANGED
@@ -1,4 +1,4 @@
|
|
1
|
-
# Copyright (c) 2014-
|
1
|
+
# Copyright (c) 2014-2021 Yegor Bugayenko
|
2
2
|
#
|
3
3
|
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
4
4
|
# of this software and associated documentation files (the 'Software'), to deal
|
@@ -34,6 +34,7 @@ module PDD
|
|
34
34
|
@xml.xpath('//puzzle').map do |p|
|
35
35
|
role = p.xpath('role/text()').to_s
|
36
36
|
next nil if @roles.include?(role)
|
37
|
+
|
37
38
|
"puzzle #{p.xpath('file/text()')}:#{p.xpath('lines/text()')}" +
|
38
39
|
if role.empty?
|
39
40
|
" doesn't define any role"\
|
data/lib/pdd/rule/text.rb
CHANGED
@@ -1,4 +1,4 @@
|
|
1
|
-
# Copyright (c) 2014-
|
1
|
+
# Copyright (c) 2014-2021 Yegor Bugayenko
|
2
2
|
#
|
3
3
|
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
4
4
|
# of this software and associated documentation files (the 'Software'), to deal
|
@@ -34,6 +34,7 @@ module PDD
|
|
34
34
|
@xml.xpath('//puzzle').map do |p|
|
35
35
|
words = p.xpath('body/text()').to_s.split.size
|
36
36
|
next nil if words >= @min
|
37
|
+
|
37
38
|
"Puzzle #{p.xpath('file/text()')}:#{p.xpath('lines/text()')}"\
|
38
39
|
" has a very short description of just #{words} words while"\
|
39
40
|
" a minimum of #{@min} is required"
|
data/lib/pdd/source.rb
CHANGED
@@ -1,4 +1,4 @@
|
|
1
|
-
# Copyright (c) 2014-
|
1
|
+
# Copyright (c) 2014-2021 Yegor Bugayenko
|
2
2
|
#
|
3
3
|
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
4
4
|
# of this software and associated documentation files (the 'Software'), to deal
|
@@ -26,6 +26,7 @@ require_relative '../pdd'
|
|
26
26
|
require_relative '../pdd/puzzle'
|
27
27
|
|
28
28
|
module PDD
|
29
|
+
MARKERS = ["\x40todo", 'TODO:?'].freeze
|
29
30
|
# Source.
|
30
31
|
class Source
|
31
32
|
# Ctor.
|
@@ -36,22 +37,27 @@ module PDD
|
|
36
37
|
@path = path
|
37
38
|
end
|
38
39
|
|
40
|
+
def match_markers(line)
|
41
|
+
MARKERS.map do |mkr|
|
42
|
+
%r{(.*(?:^|\s))#{mkr}\s+#([\w\-.:/]+)\s+(.+)}.match(line)
|
43
|
+
end.compact
|
44
|
+
end
|
45
|
+
|
39
46
|
# Fetch all puzzles.
|
40
47
|
def puzzles
|
41
|
-
PDD.log.info "Reading #{@path}..."
|
48
|
+
PDD.log.info "Reading #{@path} ..."
|
42
49
|
puzzles = []
|
43
50
|
lines = File.readlines(@file, encoding: 'UTF-8')
|
44
51
|
lines.each_with_index do |line, idx|
|
45
52
|
begin
|
46
53
|
check_rules(line)
|
47
|
-
|
48
|
-
puzzles << puzzle(lines.drop(idx + 1), m, idx)
|
49
|
-
end
|
50
|
-
%r{(.*(?:^|\s))TODO:?\s+#([\w\-\.:/]+)\s+(.+)}.match(line) do |m|
|
54
|
+
match_markers(line).each do |m|
|
51
55
|
puzzles << puzzle(lines.drop(idx + 1), m, idx)
|
52
56
|
end
|
53
57
|
rescue Error, ArgumentError => ex
|
54
|
-
|
58
|
+
message = "#{@path}:#{idx + 1} #{ex.message}"
|
59
|
+
raise Error, message unless PDD.opts && PDD.opts['skip-errors']
|
60
|
+
PDD.log.warn message
|
55
61
|
end
|
56
62
|
end
|
57
63
|
puzzles
|
@@ -61,19 +67,19 @@ module PDD
|
|
61
67
|
|
62
68
|
def get_no_leading_space_error(todo)
|
63
69
|
"#{todo} must have a leading space to become \
|
64
|
-
a puzzle, as this page explains: https://github.com/
|
70
|
+
a puzzle, as this page explains: https://github.com/cqfn/pdd#how-to-format"
|
65
71
|
end
|
66
72
|
|
67
73
|
def get_no_puzzle_marker_error(todo)
|
68
74
|
"#{todo} found, but puzzle can't be parsed, \
|
69
75
|
most probably because #{todo} is not followed by a puzzle marker, \
|
70
|
-
as this page explains: https://github.com/
|
76
|
+
as this page explains: https://github.com/cqfn/pdd#how-to-format"
|
71
77
|
end
|
72
78
|
|
73
79
|
def get_space_after_hash_error(todo)
|
74
80
|
"#{todo} found, but there is an unexpected space \
|
75
81
|
after the hash sign, it should not be there, \
|
76
|
-
see https://github.com/
|
82
|
+
see https://github.com/cqfn/pdd#how-to-format"
|
77
83
|
end
|
78
84
|
|
79
85
|
def check_rules(line)
|
@@ -100,7 +106,7 @@ see https://github.com/yegor256/pdd#how-to-format"
|
|
100
106
|
# Fetch puzzle
|
101
107
|
def puzzle(lines, match, idx)
|
102
108
|
tail = tail(lines, match[1], idx)
|
103
|
-
body =
|
109
|
+
body = "#{match[3]} #{tail.join(' ')}".gsub(/\s+/, ' ').strip
|
104
110
|
body = body.chomp('*/-->').strip
|
105
111
|
marker = marker(match[2])
|
106
112
|
Puzzle.new(
|
@@ -115,11 +121,11 @@ see https://github.com/yegor256/pdd#how-to-format"
|
|
115
121
|
|
116
122
|
# Parse a marker.
|
117
123
|
def marker(text)
|
118
|
-
re = %r{([\w
|
124
|
+
re = %r{([\w\-.]+)(?::(\d+)(?:(m|h)[a-z]*)?)?(?:/([A-Z]+))?}
|
119
125
|
match = re.match(text)
|
120
126
|
if match.nil?
|
121
127
|
raise "Invalid puzzle marker \"#{text}\", most probably formatted \
|
122
|
-
against the rules explained here: https://github.com/
|
128
|
+
against the rules explained here: https://github.com/cqfn/pdd#how-to-format"
|
123
129
|
end
|
124
130
|
{
|
125
131
|
ticket: match[1],
|
@@ -138,21 +144,15 @@ against the rules explained here: https://github.com/yegor256/pdd#how-to-format"
|
|
138
144
|
# Fetch puzzle tail (all lines after the first one)
|
139
145
|
def tail(lines, prefix, start)
|
140
146
|
lines
|
141
|
-
.take_while { |t| t.start_with?(prefix) }
|
147
|
+
.take_while { |t| match_markers(t).none? && t.start_with?(prefix) }
|
142
148
|
.map { |t| t[prefix.length, t.length] }
|
143
149
|
.take_while { |t| t =~ /^[ a-zA-Z0-9]/ }
|
144
150
|
.each_with_index do |t, i|
|
145
|
-
|
146
|
-
|
151
|
+
next if t.start_with?(' ')
|
152
|
+
|
153
|
+
raise Error, "Space expected at #{start + i + 2}:#{prefix.length}; \
|
147
154
|
make sure all lines in the puzzle body have a single leading space."
|
148
|
-
|
149
|
-
.each_with_index do |t, i|
|
150
|
-
next if t !~ /^\s{2,}/
|
151
|
-
raise Error, "Too many leading spaces \
|
152
|
-
at #{start + i + 2}:#{prefix.length}; \
|
153
|
-
make sure all lines that include the puzzle body start \
|
154
|
-
at position ##{prefix.length + 1}."
|
155
|
-
end
|
155
|
+
end
|
156
156
|
.map { |t| t[1, t.length] }
|
157
157
|
end
|
158
158
|
|
@@ -168,28 +168,29 @@ at position ##{prefix.length + 1}."
|
|
168
168
|
if `#{git} rev-parse --is-inside-work-tree 2>/dev/null`.strip == 'true'
|
169
169
|
cmd = "#{git} blame -L #{pos},#{pos} --porcelain #{name}"
|
170
170
|
add_github_login(Hash[
|
171
|
-
|
172
|
-
|
173
|
-
|
174
|
-
|
175
|
-
|
176
|
-
|
177
|
-
|
178
|
-
|
179
|
-
|
180
|
-
|
181
|
-
|
182
|
-
|
183
|
-
|
184
|
-
|
185
|
-
|
171
|
+
`#{cmd}`.split("\n").map do |line|
|
172
|
+
case line
|
173
|
+
when /^author /
|
174
|
+
[:author, line.sub(/^author /, '')]
|
175
|
+
when /^author-mail [^@]+@[^.]+\..+/
|
176
|
+
[:email, line.sub(/^author-mail <(.+)>$/, '\1')]
|
177
|
+
when /^author-time /
|
178
|
+
[
|
179
|
+
:time,
|
180
|
+
Time.at(
|
181
|
+
line.sub(/^author-time ([0-9]+)$/, '\1').to_i
|
182
|
+
).utc.iso8601
|
183
|
+
]
|
184
|
+
end
|
185
|
+
end.compact
|
186
|
+
])
|
186
187
|
else
|
187
188
|
{}
|
188
189
|
end
|
189
190
|
end
|
190
191
|
|
191
192
|
def add_github_login(info)
|
192
|
-
login = find_github_login(info
|
193
|
+
login = find_github_login(info)
|
193
194
|
info[:author] = "@#{login}" unless login.empty?
|
194
195
|
info
|
195
196
|
end
|
@@ -204,15 +205,26 @@ at position ##{prefix.length + 1}."
|
|
204
205
|
JSON.parse res.body
|
205
206
|
end
|
206
207
|
|
207
|
-
def find_github_user(
|
208
|
-
|
209
|
-
|
208
|
+
def find_github_user(info)
|
209
|
+
email, author = info.values_at(:email, :author)
|
210
|
+
# if email is not defined, changes have not been committed
|
211
|
+
return if email.nil?
|
212
|
+
|
213
|
+
base_uri = 'https://api.github.com/search/users?per_page=1'
|
214
|
+
query = base_uri + "&q=#{email}+in:email"
|
210
215
|
json = get_json query
|
216
|
+
# find user by name instead since users can make github email private
|
217
|
+
unless json['total_count'].positive?
|
218
|
+
return if author.nil?
|
219
|
+
|
220
|
+
query = base_uri + "&q=#{author}+in:fullname"
|
221
|
+
json = get_json query
|
222
|
+
end
|
211
223
|
json['items'].first
|
212
224
|
end
|
213
225
|
|
214
|
-
def find_github_login(
|
215
|
-
user = find_github_user
|
226
|
+
def find_github_login(info)
|
227
|
+
user = find_github_user info
|
216
228
|
user['login']
|
217
229
|
rescue StandardError
|
218
230
|
''
|
@@ -232,8 +244,8 @@ at position ##{prefix.length + 1}."
|
|
232
244
|
# Fetch all puzzles.
|
233
245
|
def puzzles
|
234
246
|
@source.puzzles
|
235
|
-
rescue Error =>
|
236
|
-
raise Error, "#{@file}; #{
|
247
|
+
rescue Error => e
|
248
|
+
raise Error, "#{@file}; #{e.message}"
|
237
249
|
end
|
238
250
|
end
|
239
251
|
end
|
data/lib/pdd/sources.rb
CHANGED
@@ -1,4 +1,4 @@
|
|
1
|
-
# Copyright (c) 2014-
|
1
|
+
# Copyright (c) 2014-2021 Yegor Bugayenko
|
2
2
|
#
|
3
3
|
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
4
4
|
# of this software and associated documentation files (the 'Software'), to deal
|
@@ -18,41 +18,61 @@
|
|
18
18
|
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
19
19
|
# SOFTWARE.
|
20
20
|
|
21
|
+
require 'rainbow'
|
21
22
|
require 'shellwords'
|
22
23
|
require 'English'
|
23
24
|
require_relative 'source'
|
25
|
+
require_relative '../../utils/glob'
|
24
26
|
|
25
27
|
module PDD
|
26
28
|
# Code base abstraction
|
27
29
|
class Sources
|
28
30
|
# Ctor.
|
29
31
|
# +dir+:: Directory with source code files
|
30
|
-
def initialize(dir
|
32
|
+
def initialize(dir)
|
31
33
|
@dir = File.absolute_path(dir)
|
32
|
-
@exclude =
|
34
|
+
@exclude = ['.git/**/*']
|
35
|
+
@include = []
|
33
36
|
end
|
34
37
|
|
35
38
|
# Fetch all sources.
|
36
39
|
def fetch
|
40
|
+
exclude_paths = @exclude.map do |ptn|
|
41
|
+
Glob.new(File.join(@dir, ptn)).to_regexp
|
42
|
+
end
|
37
43
|
files = Dir.glob(
|
38
44
|
File.join(@dir, '**/*'), File::FNM_DOTMATCH
|
39
|
-
).reject
|
40
|
-
|
41
|
-
@exclude.each do |ptn|
|
42
|
-
Dir.glob(File.join(@dir, ptn), File::FNM_DOTMATCH) do |f|
|
43
|
-
files.delete_if { |i| i == f }
|
44
|
-
excluded += 1
|
45
|
-
end
|
45
|
+
).reject do |f|
|
46
|
+
File.directory?(f) || exclude_paths.any? { |ptn| f.match(ptn) }
|
46
47
|
end
|
47
|
-
|
48
|
+
files += Dir.glob(
|
49
|
+
@include.map { |ptn| File.join(@dir, ptn) }
|
50
|
+
).reject { |f| File.directory?(f) }
|
51
|
+
files = files.uniq # remove duplicates
|
48
52
|
files.reject { |f| binary?(f) }.map do |file|
|
49
53
|
path = file[@dir.length + 1, file.length]
|
50
54
|
VerboseSource.new(path, Source.new(file, path))
|
51
55
|
end
|
52
56
|
end
|
53
57
|
|
54
|
-
def exclude(
|
55
|
-
|
58
|
+
def exclude(paths)
|
59
|
+
paths = paths.nil? ? [] : paths
|
60
|
+
paths = paths.is_a?(Array) ? paths : [paths]
|
61
|
+
@exclude.push(*paths)
|
62
|
+
paths&.each do |path|
|
63
|
+
PDD.log.info "#{Rainbow('Excluding').orange} #{path}"
|
64
|
+
end
|
65
|
+
self
|
66
|
+
end
|
67
|
+
|
68
|
+
def include(paths)
|
69
|
+
paths = paths.nil? ? [] : paths
|
70
|
+
paths = paths.is_a?(Array) ? paths : [paths]
|
71
|
+
@include.push(*paths)
|
72
|
+
paths&.each do |path|
|
73
|
+
PDD.log.info "#{Rainbow('Including').blue} #{path}"
|
74
|
+
end
|
75
|
+
self
|
56
76
|
end
|
57
77
|
|
58
78
|
private
|
@@ -63,6 +83,7 @@ module PDD
|
|
63
83
|
# `test_ignores_binary_files` in `test_sources.rb`.
|
64
84
|
def binary?(file)
|
65
85
|
return false if Gem.win_platform?
|
86
|
+
|
66
87
|
`grep -qI '.' #{Shellwords.escape(file)}`
|
67
88
|
if $CHILD_STATUS.success?
|
68
89
|
false
|
data/lib/pdd/version.rb
CHANGED
@@ -1,4 +1,4 @@
|
|
1
|
-
# Copyright (c) 2014-
|
1
|
+
# Copyright (c) 2014-2021 Yegor Bugayenko
|
2
2
|
#
|
3
3
|
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
4
4
|
# of this software and associated documentation files (the 'Software'), to deal
|
@@ -20,8 +20,8 @@
|
|
20
20
|
|
21
21
|
# PDD main module.
|
22
22
|
# Author:: Yegor Bugayenko (yegor256@gmail.com)
|
23
|
-
# Copyright:: Copyright (c) 2014-
|
23
|
+
# Copyright:: Copyright (c) 2014-2021 Yegor Bugayenko
|
24
24
|
# License:: MIT
|
25
25
|
module PDD
|
26
|
-
VERSION = '0.20.
|
26
|
+
VERSION = '0.20.8'.freeze
|
27
27
|
end
|