pdd 0.20.6 → 0.21.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.
Files changed (49) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/codecov.yml +20 -0
  3. data/.github/workflows/rake.yml +24 -0
  4. data/.gitignore +5 -5
  5. data/.overcommit.yml +96 -0
  6. data/.pdd +3 -0
  7. data/.rubocop.yml +3 -4
  8. data/.rultor.yml +8 -15
  9. data/.simplecov +6 -6
  10. data/Gemfile +1 -1
  11. data/LICENSE.txt +1 -1
  12. data/README.md +67 -37
  13. data/Rakefile +7 -1
  14. data/assets/puzzles.xsd +1 -1
  15. data/assets/puzzles.xsl +1 -1
  16. data/bin/pdd +31 -18
  17. data/features/catches_broken_puzzles.feature +3 -36
  18. data/features/cli.feature +1 -1
  19. data/features/html_output.feature +1 -1
  20. data/features/parsing.feature +31 -1
  21. data/features/rake.feature +14 -12
  22. data/features/step_definitions/steps.rb +7 -10
  23. data/features/support/env.rb +1 -1
  24. data/features/uses_config.feature +1 -1
  25. data/lib/pdd/puzzle.rb +1 -1
  26. data/lib/pdd/rake_task.rb +11 -12
  27. data/lib/pdd/rule/duplicates.rb +2 -1
  28. data/lib/pdd/rule/estimates.rb +1 -1
  29. data/lib/pdd/rule/roles.rb +2 -1
  30. data/lib/pdd/rule/text.rb +2 -1
  31. data/lib/pdd/source.rb +86 -75
  32. data/lib/pdd/sources.rb +42 -21
  33. data/lib/pdd/version.rb +3 -3
  34. data/lib/pdd.rb +20 -13
  35. data/pdd.gemspec +8 -5
  36. data/test/test__helper.rb +15 -2
  37. data/test/test_duplicates.rb +2 -2
  38. data/test/test_estimates.rb +2 -2
  39. data/test/test_pdd.rb +4 -2
  40. data/test/test_rake_task.rb +7 -4
  41. data/test/test_roles.rb +2 -2
  42. data/test/test_source.rb +168 -51
  43. data/test/test_source_todo.rb +38 -29
  44. data/test/test_sources.rb +18 -3
  45. data/test/test_text.rb +2 -2
  46. data/utils/glob.rb +65 -0
  47. metadata +37 -7
  48. data/.travis.yml +0 -13
  49. data/appveyor.yml +0 -21
@@ -11,45 +11,12 @@ Feature: Catches Broken Puzzles
11
11
  * Some other documentation
12
12
  * text that is not relevant to
13
13
  * the puzzle below.
14
- * @todo #13 This puzzle has an incorrect format
15
- * because it doesn't start with a space on
16
- * the second and the third lines
14
+ * @todo This puzzle has an incorrect format
15
+ * because it doesn't have a ticket number
17
16
  */
18
17
  public void main(String[] args) {
19
18
  // later
20
19
  }
21
20
  }
22
21
  """
23
- When I run pdd it fails with "Space expected"
24
- When I run pdd it fails with "puzzle at line #6"
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"
40
-
41
- Scenario: Throwing exception on yet another broken puzzle
42
- Given I have a "Sample.java" file with content:
43
- """
44
- public class Main {
45
- //
46
- // @todo #13 This puzzle has an incorrect format
47
- // because there is no space character in the
48
- // second and third lines
49
- //
50
- public void main(String[] args) {
51
- // later
52
- }
53
- }
54
- """
55
- When I run pdd it fails with "Space expected"
22
+ When I run pdd it fails with "Sample.java:6"
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
 
@@ -18,5 +18,5 @@ Feature: HTML output
18
18
  """
19
19
  When I run bin/pdd with "-v -s . -f out.html --format=html"
20
20
  Then Exit code is zero
21
- And Stdout contains "Reading ."
21
+ And Stdout contains "Reading from root dir ."
22
22
  And XML file "out.html" matches "/html/body"
@@ -25,6 +25,32 @@ Feature: Parsing
25
25
  And XML matches "//puzzle[role='DEV']"
26
26
  And XML matches "//puzzle[estimate='0']"
27
27
 
28
+ Scenario: Simple puzzle within comment block
29
+ Given I have a "test/a/b/Sample.java" file with content:
30
+ """
31
+ public class Main {
32
+ /**
33
+ * Some other documentation
34
+ * text that is not relevant to
35
+ * the puzzle below.
36
+ * @todo #13 This puzzle has a correct format
37
+ * It doesn't start with a space on
38
+ * the second and the third lines
39
+ */
40
+ public void main(String[] args) {
41
+ // later
42
+ }
43
+ }
44
+ """
45
+ When I run pdd
46
+ Then XML matches "/puzzles[count(puzzle)=1]"
47
+ And XML matches "//puzzle[file='test/a/b/Sample.java']"
48
+ And XML matches "//puzzle[ticket='13']"
49
+ And XML matches "//puzzle[lines='6-8']"
50
+ And XML matches "//puzzle[starts-with(body,'This')]"
51
+ And XML matches "//puzzle[role='DEV']"
52
+ And XML matches "//puzzle[estimate='0']"
53
+
28
54
  Scenario: Multiple puzzles in one file
29
55
  Given I have a "test/a/b/c/Sample.java" file with content:
30
56
  """
@@ -34,6 +60,9 @@ Feature: Parsing
34
60
  * @todo #ABC-67:15min And this one ever later
35
61
  * @todo #F-78-3:2h/DEV This is for a developer
36
62
  * who will join us later
63
+ * @todo #44 This puzzle has a correct format
64
+ * even though it doesn't start with a space on
65
+ * the second and the third lines
37
66
  */
38
67
  public void main(String[] args) {
39
68
  // later
@@ -41,10 +70,11 @@ Feature: Parsing
41
70
  }
42
71
  """
43
72
  When I run pdd
44
- Then XML matches "/puzzles[count(puzzle)=3]"
73
+ Then XML matches "/puzzles[count(puzzle)=4]"
45
74
  And XML matches "//puzzle[ticket='13' and lines='3-3']"
46
75
  And XML matches "//puzzle[ticket='13' and body='This one later']"
47
76
  And XML matches "//puzzle[ticket='ABC-67' and lines='4-4']"
48
77
  And XML matches "//puzzle[ticket='F-78-3' and lines='5-6']"
49
78
  And XML matches "//puzzle[ticket='ABC-67' and estimate='15']"
50
79
  And XML matches "//puzzle[ticket='F-78-3' and estimate='120']"
80
+ And XML matches "//puzzle[ticket='44' and lines='7-9']"
@@ -1,19 +1,21 @@
1
1
  Feature: Rake Task
2
- Scenario: For now we failed if run 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
3
5
  Given It is Unix
4
- And a file named "Rakefile" with:
6
+ And I have a "Rakefile" file with content:
5
7
  """
6
8
  require 'pdd/rake_task'
7
- PDD::RakeTask.new
9
+ PDD::RakeTask.new(:pdd) do |task|
10
+ task.includes = ['a.txt']
11
+ end
8
12
  """
9
- When I run the following commands with `bash`:
10
- """bash
11
- rake pdd
13
+ And I have a "a.txt" file with content:
12
14
  """
13
- Then the exit status should be 1
14
- And the stderr should contain:
15
+ \x40todo #55 hello!
15
16
  """
16
- NOT IMPLEMENTED
17
- """
18
- When I remove a file named "Rakefile" with full force
19
- Then a file named "Rakefile" does not exist
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-2020 Yegor Bugayenko
1
+ # Copyright (c) 2014-2022 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 => ex
68
- unless ex.message.include?(txt)
69
- raise "PDD failed but exception doesn't contain \"#{txt}\": #{ex.message}"
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
@@ -79,9 +79,7 @@ When(%r{^I run bin/pdd with "([^"]*)"$}) do |arg|
79
79
  end
80
80
 
81
81
  Then(/^Stdout contains "([^"]*)"$/) do |txt|
82
- unless @stdout.include?(txt)
83
- raise "STDOUT doesn't contain '#{txt}':\n#{@stdout}"
84
- end
82
+ raise "STDOUT doesn't contain '#{txt}':\n#{@stdout}" unless @stdout.include?(txt)
85
83
  end
86
84
 
87
85
  Then(/^Stdout is empty$/) do
@@ -90,11 +88,10 @@ end
90
88
 
91
89
  Then(/^XML file "([^"]+)" matches "([^"]+)"$/) do |file, xpath|
92
90
  raise "File #{file} doesn't exit" unless File.exist?(file)
91
+
93
92
  xml = Nokogiri::XML.parse(File.read(file))
94
93
  xml.remove_namespaces!
95
- if xml.xpath(xpath).empty?
96
- raise "XML file #{file} doesn't match \"#{xpath}\":\n#{xml}"
97
- end
94
+ raise "XML file #{file} doesn't match \"#{xpath}\":\n#{xml}" if xml.xpath(xpath).empty?
98
95
  end
99
96
 
100
97
  Then(/^Exit code is zero$/) do
@@ -1,4 +1,4 @@
1
- # Copyright (c) 2014-2020 Yegor Bugayenko
1
+ # Copyright (c) 2014-2022 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
@@ -16,6 +16,6 @@ Feature: Using .pdd config file
16
16
  """
17
17
  When I run bin/pdd with ""
18
18
  Then Exit code is zero
19
- And Stdout contains "Reading ."
19
+ And Stdout contains "Reading from root dir ."
20
20
  And XML file "out.xml" matches "/puzzles[count(puzzle)=1]"
21
21
 
data/lib/pdd/puzzle.rb CHANGED
@@ -1,4 +1,4 @@
1
- # Copyright (c) 2014-2020 Yegor Bugayenko
1
+ # Copyright (c) 2014-2022 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
data/lib/pdd/rake_task.rb CHANGED
@@ -1,30 +1,29 @@
1
1
  require 'rake'
2
2
  require 'rake/tasklib'
3
+ require 'minitest/autorun'
4
+ require 'nokogiri'
5
+ require 'tmpdir'
6
+ require 'slop'
7
+ require 'pdd'
3
8
 
4
9
  # PDD Rake task
5
10
  module PDD
6
11
  # Rake task
7
12
  class RakeTask < Rake::TaskLib
8
- attr_accessor :name
13
+ attr_accessor :name, :fail_on_error, :includes, :license, :quiet
14
+
9
15
  def initialize(*args, &task_block)
10
- # @todo #125:30m Needs to have more parameters to run this task.
11
- # For now, we just have a single parameter - the name.
12
- # Needs more parameters like the xcop rake task has.
13
16
  @name = args.shift || :pdd
17
+ @includes = []
18
+ @excludes = []
19
+ @license = nil
20
+ @quiet = false
14
21
  desc 'Run PDD' unless ::Rake.application.last_description
15
22
  task(name, *args) do |_, task_args|
16
23
  RakeFileUtils.send(:verbose, true) do
17
24
  yield(*[self, task_args].slice(0, task_block.arity)) if block_given?
18
- run
19
25
  end
20
26
  end
21
27
  end
22
-
23
- def run
24
- # @todo #125:30m need to implement this method.
25
- # For now, it's just a task,
26
- # that accepts one parameter and throws a system error.
27
- abort('NOT IMPLEMENTED')
28
- end
29
28
  end
30
29
  end
@@ -1,4 +1,4 @@
1
- # Copyright (c) 2014-2020 Yegor Bugayenko
1
+ # Copyright (c) 2014-2022 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()')}"
@@ -1,4 +1,4 @@
1
- # Copyright (c) 2014-2020 Yegor Bugayenko
1
+ # Copyright (c) 2014-2022 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
@@ -1,4 +1,4 @@
1
- # Copyright (c) 2014-2020 Yegor Bugayenko
1
+ # Copyright (c) 2014-2022 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-2020 Yegor Bugayenko
1
+ # Copyright (c) 2014-2022 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-2020 Yegor Bugayenko
1
+ # Copyright (c) 2014-2022 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,21 +37,47 @@ module PDD
36
37
  @path = path
37
38
  end
38
39
 
40
+ def match_markers(l)
41
+ if l.downcase.include? 'todo'
42
+ /[^\s]\x40todo/.match(l) do |_|
43
+ raise Error, get_no_leading_space_error("\x40todo")
44
+ end
45
+ /\x40todo(?!\s+#)/.match(l) do |_|
46
+ raise Error, get_no_puzzle_marker_error("\x40todo")
47
+ end
48
+ /\x40todo\s+#\s/.match(l) do |_|
49
+ raise Error, get_space_after_hash_error("\x40todo")
50
+ end
51
+ /[^\s]TODO:?/.match(l) do |_|
52
+ raise Error, get_no_leading_space_error('TODO')
53
+ end
54
+ /TODO(?!:?\s+#)/.match(l) do |_|
55
+ raise Error, get_no_puzzle_marker_error('TODO')
56
+ end
57
+ /TODO:?\s+#\s/.match(l) do |_|
58
+ raise Error, get_space_after_hash_error('TODO')
59
+ end
60
+ a = [%r{(.*(?:^|\s))(?:\x40todo|TODO:|TODO)\s+#([\w\-.:/]+)\s+(.+)}.match(l)]
61
+ a.compact
62
+ else
63
+ []
64
+ end
65
+ end
66
+
39
67
  # Fetch all puzzles.
40
68
  def puzzles
41
- PDD.log.info "Reading #{@path}..."
69
+ PDD.log.info "Reading #{@path} ..."
42
70
  puzzles = []
43
71
  lines = File.readlines(@file, encoding: 'UTF-8')
44
72
  lines.each_with_index do |line, idx|
45
73
  begin
46
- check_rules(line)
47
- ["\x40todo", 'TODO:?'].each do |pfx|
48
- %r{(.*(?:^|\s))#{pfx}\s+#([\w\-\.:/]+)\s+(.+)}.match(line) do |m|
49
- puzzles << puzzle(lines.drop(idx + 1), m, idx)
50
- end
74
+ match_markers(line).each do |m|
75
+ puzzles << puzzle(lines.drop(idx + 1), m, idx)
51
76
  end
52
77
  rescue Error, ArgumentError => ex
53
- raise Error, "puzzle at line ##{idx + 1}; #{ex.message}"
78
+ message = "#{@path}:#{idx + 1} #{ex.message}"
79
+ raise Error, message unless PDD.opts && PDD.opts['skip-errors']
80
+ PDD.log.warn message
54
81
  end
55
82
  end
56
83
  puzzles
@@ -60,46 +87,25 @@ module PDD
60
87
 
61
88
  def get_no_leading_space_error(todo)
62
89
  "#{todo} must have a leading space to become \
63
- a puzzle, as this page explains: https://github.com/yegor256/pdd#how-to-format"
90
+ a puzzle, as this page explains: https://github.com/cqfn/pdd#how-to-format"
64
91
  end
65
92
 
66
93
  def get_no_puzzle_marker_error(todo)
67
94
  "#{todo} found, but puzzle can't be parsed, \
68
95
  most probably because #{todo} is not followed by a puzzle marker, \
69
- as this page explains: https://github.com/yegor256/pdd#how-to-format"
96
+ as this page explains: https://github.com/cqfn/pdd#how-to-format"
70
97
  end
71
98
 
72
99
  def get_space_after_hash_error(todo)
73
100
  "#{todo} found, but there is an unexpected space \
74
101
  after the hash sign, it should not be there, \
75
- see https://github.com/yegor256/pdd#how-to-format"
76
- end
77
-
78
- def check_rules(line)
79
- /[^\s]\x40todo/.match(line) do |_|
80
- raise Error, get_no_leading_space_error("\x40todo")
81
- end
82
- /\x40todo(?!\s+#)/.match(line) do |_|
83
- raise Error, get_no_puzzle_marker_error("\x40todo")
84
- end
85
- /\x40todo\s+#\s/.match(line) do |_|
86
- raise Error, get_space_after_hash_error("\x40todo")
87
- end
88
- /[^\s]TODO:?/.match(line) do |_|
89
- raise Error, get_no_leading_space_error('TODO')
90
- end
91
- /TODO(?!:?\s+#)/.match(line) do |_|
92
- raise Error, get_no_puzzle_marker_error('TODO')
93
- end
94
- /TODO:?\s+#\s/.match(line) do |_|
95
- raise Error, get_space_after_hash_error('TODO')
96
- end
102
+ see https://github.com/cqfn/pdd#how-to-format"
97
103
  end
98
104
 
99
105
  # Fetch puzzle
100
106
  def puzzle(lines, match, idx)
101
- tail = tail(lines, match[1], idx)
102
- body = (match[3] + ' ' + tail.join(' ')).gsub(/\s+/, ' ').strip
107
+ tail = tail(lines, match[1])
108
+ body = "#{match[3]} #{tail.join(' ')}".gsub(/\s+/, ' ').strip
103
109
  body = body.chomp('*/-->').strip
104
110
  marker = marker(match[2])
105
111
  Puzzle.new(
@@ -114,11 +120,11 @@ see https://github.com/yegor256/pdd#how-to-format"
114
120
 
115
121
  # Parse a marker.
116
122
  def marker(text)
117
- re = %r{([\w\-\.]+)(?::(\d+)(?:(m|h)[a-z]*)?)?(?:/([A-Z]+))?}
123
+ re = %r{([\w\-.]+)(?::(\d+)(?:(m|h)[a-z]*)?)?(?:/([A-Z]+))?}
118
124
  match = re.match(text)
119
125
  if match.nil?
120
126
  raise "Invalid puzzle marker \"#{text}\", most probably formatted \
121
- against the rules explained here: https://github.com/yegor256/pdd#how-to-format"
127
+ against the rules explained here: https://github.com/cqfn/pdd#how-to-format"
122
128
  end
123
129
  {
124
130
  ticket: match[1],
@@ -135,24 +141,17 @@ against the rules explained here: https://github.com/yegor256/pdd#how-to-format"
135
141
  end
136
142
 
137
143
  # Fetch puzzle tail (all lines after the first one)
138
- def tail(lines, prefix, start)
144
+ def tail(lines, prefix)
145
+ prefix = prefix.rstrip
139
146
  lines
140
- .take_while { |t| t.start_with?(prefix) }
141
- .map { |t| t[prefix.length, t.length] }
142
- .take_while { |t| t =~ /^[ a-zA-Z0-9]/ }
143
- .each_with_index do |t, i|
144
- next if t.start_with?(' ')
145
- raise Error, "Space expected at #{start + i + 2}:#{prefix.length}; \
146
- make sure all lines in the puzzle body have a single leading space."
147
- end
148
- .each_with_index do |t, i|
149
- next if t !~ /^\s{2,}/
150
- raise Error, "Too many leading spaces \
151
- at #{start + i + 2}:#{prefix.length}; \
152
- make sure all lines that include the puzzle body start \
153
- at position ##{prefix.length + 1}."
147
+ .take_while { |t| match_markers(t).none? && t.start_with?(prefix) }
148
+ .take_while do |t|
149
+ # account for carriage return in line endings
150
+ t_len = t.length - 1
151
+ t_len <= prefix.length || t_len > prefix.length + 2
154
152
  end
155
- .map { |t| t[1, t.length] }
153
+ .map { |t| t[prefix.length, t.length] }
154
+ .map { |t| t.start_with?(' ') ? t[1, t.length] : t }
156
155
  end
157
156
 
158
157
  # @todo #75:30min Let's make it possible to fetch Subversion data
@@ -167,28 +166,29 @@ at position ##{prefix.length + 1}."
167
166
  if `#{git} rev-parse --is-inside-work-tree 2>/dev/null`.strip == 'true'
168
167
  cmd = "#{git} blame -L #{pos},#{pos} --porcelain #{name}"
169
168
  add_github_login(Hash[
170
- `#{cmd}`.split("\n").map do |line|
171
- if line =~ /^author /
172
- [:author, line.sub(/^author /, '')]
173
- elsif line =~ /^author-mail [^@]+@[^\.]+\..+/
174
- [:email, line.sub(/^author-mail <(.+)>$/, '\1')]
175
- elsif line =~ /^author-time /
176
- [
177
- :time,
178
- Time.at(
179
- line.sub(/^author-time ([0-9]+)$/, '\1').to_i
180
- ).utc.iso8601
181
- ]
182
- end
183
- end.compact
184
- ])
169
+ `#{cmd}`.split("\n").map do |line|
170
+ case line
171
+ when /^author /
172
+ [:author, line.sub(/^author /, '')]
173
+ when /^author-mail [^@]+@[^.]+\..+/
174
+ [:email, line.sub(/^author-mail <(.+)>$/, '\1')]
175
+ when /^author-time /
176
+ [
177
+ :time,
178
+ Time.at(
179
+ line.sub(/^author-time ([0-9]+)$/, '\1').to_i
180
+ ).utc.iso8601
181
+ ]
182
+ end
183
+ end.compact
184
+ ])
185
185
  else
186
186
  {}
187
187
  end
188
188
  end
189
189
 
190
190
  def add_github_login(info)
191
- login = find_github_login(info[:email])
191
+ login = find_github_login(info)
192
192
  info[:author] = "@#{login}" unless login.empty?
193
193
  info
194
194
  end
@@ -203,15 +203,26 @@ at position ##{prefix.length + 1}."
203
203
  JSON.parse res.body
204
204
  end
205
205
 
206
- def find_github_user(email)
207
- base_uri = 'https://api.github.com/search/users'
208
- query = base_uri + "?q=#{email}+in:email&perpage=1"
206
+ def find_github_user(info)
207
+ email, author = info.values_at(:email, :author)
208
+ # if email is not defined, changes have not been committed
209
+ return if email.nil?
210
+
211
+ base_uri = 'https://api.github.com/search/users?per_page=1'
212
+ query = base_uri + "&q=#{email}+in:email"
209
213
  json = get_json query
214
+ # find user by name instead since users can make github email private
215
+ unless json['total_count'].positive?
216
+ return if author.nil?
217
+
218
+ query = base_uri + "&q=#{author}+in:fullname"
219
+ json = get_json query
220
+ end
210
221
  json['items'].first
211
222
  end
212
223
 
213
- def find_github_login(email)
214
- user = find_github_user email
224
+ def find_github_login(info)
225
+ user = find_github_user info
215
226
  user['login']
216
227
  rescue StandardError
217
228
  ''
@@ -231,8 +242,8 @@ at position ##{prefix.length + 1}."
231
242
  # Fetch all puzzles.
232
243
  def puzzles
233
244
  @source.puzzles
234
- rescue Error => ex
235
- raise Error, "#{@file}; #{ex.message}"
245
+ rescue Error => e
246
+ raise Error, "#{@file}; #{e.message}"
236
247
  end
237
248
  end
238
249
  end