pdd 0.20.7 → 0.21.1

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 (50) 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 -1
  7. data/.rubocop.yml +5 -6
  8. data/.rultor.yml +8 -15
  9. data/.simplecov +6 -6
  10. data/CITATION.cff +25 -0
  11. data/Gemfile +1 -1
  12. data/LICENSE.txt +1 -1
  13. data/README.md +46 -16
  14. data/Rakefile +7 -1
  15. data/assets/puzzles.xsd +1 -1
  16. data/assets/puzzles.xsl +1 -1
  17. data/bin/pdd +22 -21
  18. data/features/catches_broken_puzzles.feature +2 -20
  19. data/features/cli.feature +1 -1
  20. data/features/html_output.feature +1 -1
  21. data/features/parsing.feature +31 -1
  22. data/features/rake.feature +14 -12
  23. data/features/step_definitions/steps.rb +7 -10
  24. data/features/support/env.rb +1 -1
  25. data/features/uses_config.feature +1 -1
  26. data/lib/pdd/puzzle.rb +1 -1
  27. data/lib/pdd/rake_task.rb +11 -12
  28. data/lib/pdd/rule/duplicates.rb +2 -1
  29. data/lib/pdd/rule/estimates.rb +1 -1
  30. data/lib/pdd/rule/roles.rb +2 -1
  31. data/lib/pdd/rule/text.rb +2 -1
  32. data/lib/pdd/source.rb +58 -54
  33. data/lib/pdd/sources.rb +40 -32
  34. data/lib/pdd/version.rb +3 -3
  35. data/lib/pdd.rb +16 -16
  36. data/pdd.gemspec +7 -4
  37. data/test/test__helper.rb +2 -2
  38. data/test/test_duplicates.rb +2 -2
  39. data/test/test_estimates.rb +2 -2
  40. data/test/test_pdd.rb +3 -2
  41. data/test/test_rake_task.rb +7 -4
  42. data/test/test_roles.rb +2 -2
  43. data/test/test_source.rb +74 -17
  44. data/test/test_source_todo.rb +15 -21
  45. data/test/test_sources.rb +7 -6
  46. data/test/test_text.rb +2 -2
  47. data/utils/glob.rb +65 -0
  48. metadata +36 -5
  49. data/.travis.yml +0 -13
  50. data/appveyor.yml +0 -21
@@ -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-2021 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-2021 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-2021 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-2021 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-2021 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-2021 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-2021 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-2021 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
@@ -37,10 +37,31 @@ module PDD
37
37
  @path = path
38
38
  end
39
39
 
40
- def match_markers(line)
41
- MARKERS.map do |mkr|
42
- %r{(.*(?:^|\s))#{mkr}\s+#([\w\-\.:/]+)\s+(.+)}.match(line)
43
- end.compact
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
44
65
  end
45
66
 
46
67
  # Fetch all puzzles.
@@ -50,7 +71,6 @@ module PDD
50
71
  lines = File.readlines(@file, encoding: 'UTF-8')
51
72
  lines.each_with_index do |line, idx|
52
73
  begin
53
- check_rules(line)
54
74
  match_markers(line).each do |m|
55
75
  puzzles << puzzle(lines.drop(idx + 1), m, idx)
56
76
  end
@@ -82,31 +102,11 @@ after the hash sign, it should not be there, \
82
102
  see https://github.com/cqfn/pdd#how-to-format"
83
103
  end
84
104
 
85
- def check_rules(line)
86
- /[^\s]\x40todo/.match(line) do |_|
87
- raise Error, get_no_leading_space_error("\x40todo")
88
- end
89
- /\x40todo(?!\s+#)/.match(line) do |_|
90
- raise Error, get_no_puzzle_marker_error("\x40todo")
91
- end
92
- /\x40todo\s+#\s/.match(line) do |_|
93
- raise Error, get_space_after_hash_error("\x40todo")
94
- end
95
- /[^\s]TODO:?/.match(line) do |_|
96
- raise Error, get_no_leading_space_error('TODO')
97
- end
98
- /TODO(?!:?\s+#)/.match(line) do |_|
99
- raise Error, get_no_puzzle_marker_error('TODO')
100
- end
101
- /TODO:?\s+#\s/.match(line) do |_|
102
- raise Error, get_space_after_hash_error('TODO')
103
- end
104
- end
105
-
106
105
  # Fetch puzzle
107
106
  def puzzle(lines, match, idx)
108
- tail = tail(lines, match[1], idx)
109
- body = (match[3] + ' ' + tail.join(' ')).gsub(/\s+/, ' ').strip
107
+ col_idx = match[0].length - match[0].lstrip.length
108
+ tail = tail(lines, match[1], col_idx)
109
+ body = "#{match[3]} #{tail.join(' ')}".gsub(/\s+/, ' ').strip
110
110
  body = body.chomp('*/-->').strip
111
111
  marker = marker(match[2])
112
112
  Puzzle.new(
@@ -121,7 +121,7 @@ see https://github.com/cqfn/pdd#how-to-format"
121
121
 
122
122
  # Parse a marker.
123
123
  def marker(text)
124
- re = %r{([\w\-\.]+)(?::(\d+)(?:(m|h)[a-z]*)?)?(?:/([A-Z]+))?}
124
+ re = %r{([\w\-.]+)(?::(\d+)(?:(m|h)[a-z]*)?)?(?:/([A-Z]+))?}
125
125
  match = re.match(text)
126
126
  if match.nil?
127
127
  raise "Invalid puzzle marker \"#{text}\", most probably formatted \
@@ -143,16 +143,17 @@ against the rules explained here: https://github.com/cqfn/pdd#how-to-format"
143
143
 
144
144
  # Fetch puzzle tail (all lines after the first one)
145
145
  def tail(lines, prefix, start)
146
+ prefix = prefix.rstrip
147
+ prefix = " #{' ' * start}" if prefix.empty? # fallback to space indentation
146
148
  lines
147
149
  .take_while { |t| match_markers(t).none? && t.start_with?(prefix) }
148
- .map { |t| t[prefix.length, t.length] }
149
- .take_while { |t| t =~ /^[ a-zA-Z0-9]/ }
150
- .each_with_index do |t, i|
151
- next if t.start_with?(' ')
152
- raise Error, "Space expected at #{start + i + 2}:#{prefix.length}; \
153
- make sure all lines in the puzzle body have a single leading space."
150
+ .take_while do |t|
151
+ # account for carriage return in line endings
152
+ t_len = t.length - 1
153
+ t_len <= prefix.length || t_len > prefix.length + 2
154
154
  end
155
- .map { |t| t[1, t.length] }
155
+ .map { |t| t[prefix.length, t.length] }
156
+ .map { |t| t.start_with?(' ') ? t[1, t.length] : t }
156
157
  end
157
158
 
158
159
  # @todo #75:30min Let's make it possible to fetch Subversion data
@@ -167,21 +168,22 @@ make sure all lines in the puzzle body have a single leading space."
167
168
  if `#{git} rev-parse --is-inside-work-tree 2>/dev/null`.strip == 'true'
168
169
  cmd = "#{git} blame -L #{pos},#{pos} --porcelain #{name}"
169
170
  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
- ])
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
+ ])
185
187
  else
186
188
  {}
187
189
  end
@@ -207,12 +209,14 @@ make sure all lines in the puzzle body have a single leading space."
207
209
  email, author = info.values_at(:email, :author)
208
210
  # if email is not defined, changes have not been committed
209
211
  return if email.nil?
212
+
210
213
  base_uri = 'https://api.github.com/search/users?per_page=1'
211
214
  query = base_uri + "&q=#{email}+in:email"
212
215
  json = get_json query
213
216
  # find user by name instead since users can make github email private
214
217
  unless json['total_count'].positive?
215
218
  return if author.nil?
219
+
216
220
  query = base_uri + "&q=#{author}+in:fullname"
217
221
  json = get_json query
218
222
  end
@@ -240,8 +244,8 @@ make sure all lines in the puzzle body have a single leading space."
240
244
  # Fetch all puzzles.
241
245
  def puzzles
242
246
  @source.puzzles
243
- rescue Error => ex
244
- raise Error, "#{@file}; #{ex.message}"
247
+ rescue Error => e
248
+ raise Error, "#{@file}; #{e.message}"
245
249
  end
246
250
  end
247
251
  end
data/lib/pdd/sources.rb CHANGED
@@ -1,4 +1,4 @@
1
- # Copyright (c) 2014-2021 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
@@ -18,71 +18,79 @@
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 'shellwords'
21
+ require 'rainbow'
22
22
  require 'English'
23
+ require 'filemagic'
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, ptns = [])
32
+ def initialize(dir)
31
33
  @dir = File.absolute_path(dir)
32
- @exclude = ptns + ['.git/**/*']
33
- @include = ptns + ['.git/**/*']
34
+ @exclude = ['.git/**/*']
35
+ @include = []
34
36
  end
35
37
 
36
38
  # Fetch all sources.
37
39
  def fetch
40
+ exclude_paths = @exclude.map do |ptn|
41
+ Glob.new(File.join(@dir, ptn)).to_regexp
42
+ end
38
43
  files = Dir.glob(
39
44
  File.join(@dir, '**/*'), File::FNM_DOTMATCH
40
- ).reject { |f| File.directory?(f) }
41
- included = 0
42
- @include.each do |ptn|
43
- Dir.glob(File.join(@dir, ptn), File::FNM_DOTMATCH) do |f|
44
- files.keep_if { |i| i != f }
45
- included += 1
46
- end
45
+ ).reject do |f|
46
+ File.directory?(f) || exclude_paths.any? { |ptn| f.match(ptn) }
47
47
  end
48
- PDD.log.info "#{files.size} file(s) found, #{included} files included"
49
- excluded = 0
50
- @exclude.each do |ptn|
51
- Dir.glob(File.join(@dir, ptn), File::FNM_DOTMATCH) do |f|
52
- files.delete_if { |i| i == f }
53
- excluded += 1
54
- end
55
- end
56
- PDD.log.info "#{files.size} file(s) found, #{excluded} excluded"
48
+ files += Dir.glob(
49
+ @include.map { |ptn| File.join(@dir, ptn) }
50
+ ).reject { |f| File.directory?(f) }
51
+ files = files.uniq # remove duplicates
57
52
  files.reject { |f| binary?(f) }.map do |file|
58
53
  path = file[@dir.length + 1, file.length]
59
54
  VerboseSource.new(path, Source.new(file, path))
60
55
  end
61
56
  end
62
57
 
63
- def exclude(ptn)
64
- Sources.new(@dir, @exclude.push(ptn))
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
65
66
  end
66
67
 
67
- def include(ptn)
68
- Sources.new(@dir, @include.push(ptn))
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
69
76
  end
70
77
 
71
78
  private
72
79
 
73
- # @todo #98:30min Change the implementation of this method
74
- # to also work in Windows machines. Investigate the possibility
75
- # of use a gem for this. After that, remove the skip of the test
76
- # `test_ignores_binary_files` in `test_sources.rb`.
77
80
  def binary?(file)
78
- return false if Gem.win_platform?
79
- `grep -qI '.' #{Shellwords.escape(file)}`
80
- if $CHILD_STATUS.success?
81
+ if text_file?(file)
81
82
  false
82
83
  else
83
84
  PDD.log.info "#{file} is a binary file (#{File.size(file)} bytes)"
84
85
  true
85
86
  end
86
87
  end
88
+
89
+ def text_file?(file)
90
+ fm = FileMagic.new(FileMagic::MAGIC_MIME)
91
+ fm.file(file) =~ %r{^text/}
92
+ ensure
93
+ fm.close
94
+ end
87
95
  end
88
96
  end
data/lib/pdd/version.rb CHANGED
@@ -1,4 +1,4 @@
1
- # Copyright (c) 2014-2021 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
@@ -20,8 +20,8 @@
20
20
 
21
21
  # PDD main module.
22
22
  # Author:: Yegor Bugayenko (yegor256@gmail.com)
23
- # Copyright:: Copyright (c) 2014-2021 Yegor Bugayenko
23
+ # Copyright:: Copyright (c) 2014-2022 Yegor Bugayenko
24
24
  # License:: MIT
25
25
  module PDD
26
- VERSION = '0.20.7'.freeze
26
+ VERSION = '0.21.1'.freeze
27
27
  end
data/lib/pdd.rb CHANGED
@@ -1,4 +1,4 @@
1
- # Copyright (c) 2014-2021 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
@@ -30,7 +30,7 @@ require_relative 'pdd/rule/roles'
30
30
 
31
31
  # PDD main module.
32
32
  # Author:: Yegor Bugayenko (yegor256@gmail.com)
33
- # Copyright:: Copyright (c) 2014-2021 Yegor Bugayenko
33
+ # Copyright:: Copyright (c) 2014-2022 Yegor Bugayenko
34
34
  # License:: MIT
35
35
  module PDD
36
36
  # If it breaks.
@@ -52,11 +52,12 @@ module PDD
52
52
  # Get logger.
53
53
  def self.log
54
54
  unless defined?(@logger)
55
- @logger = Logger.new(STDOUT)
55
+ @logger = Logger.new($stdout)
56
56
  @logger.formatter = proc { |severity, _, _, msg|
57
- if severity == 'ERROR'
57
+ case severity
58
+ when 'ERROR'
58
59
  "#{Rainbow(severity).red}: #{msg}\n"
59
- elsif severity == 'WARN'
60
+ when 'WARN'
60
61
  "#{Rainbow(severity).orange}: #{msg}\n"
61
62
  else
62
63
  "#{msg}\n"
@@ -87,20 +88,15 @@ module PDD
87
88
 
88
89
  # Generate XML.
89
90
  def xml
90
- dir = @opts[:source] ? @opts[:source] : Dir.pwd
91
- PDD.log.info "Reading #{dir}"
91
+ dir = @opts[:source] || Dir.pwd
92
+ PDD.log.info "Reading from root dir #{dir}"
92
93
  require_relative 'pdd/sources'
93
- sources = Sources.new(dir)
94
- @opts[:include]&.each do |p|
95
- sources = sources.include(p)
96
- end
97
- @opts[:exclude]&.each do |p|
98
- sources = sources.exclude(p)
99
- PDD.log.info "Excluding #{p}"
100
- end
94
+ sources = Sources.new(File.expand_path(dir))
95
+ sources.exclude((@opts[:exclude] || []) + (@opts['skip-gitignore'] || []))
96
+ sources.include(@opts[:include])
101
97
  sanitize(
102
98
  rules(
103
- Nokogiri::XML::Builder.new do |xml|
99
+ Nokogiri::XML::Builder.new(encoding: 'UTF-8') do |xml|
104
100
  xml << "<?xml-stylesheet type='text/xsl' href='#{xsl}'?>"
105
101
  xml.puzzles(attrs) do
106
102
  sources.fetch.each do |source|
@@ -152,16 +148,19 @@ module PDD
152
148
  unless list.select { |r| r.start_with?('max-duplicates:') }.empty?
153
149
  raise PDD::Error, 'You can\'t modify max-duplicates, it\'s always 1'
154
150
  end
151
+
155
152
  list.push('max-duplicates:1').map do |r|
156
153
  name, value = r.split(':')
157
154
  rule = RULES[name]
158
155
  raise "Rule '#{name}' doesn't exist" if rule.nil?
156
+
159
157
  rule.new(doc, value).errors.each do |e|
160
158
  PDD.log.error e
161
159
  total += 1
162
160
  end
163
161
  end
164
162
  raise PDD::Error, "#{total} errors, see log above" unless total.zero?
163
+
165
164
  xml
166
165
  end
167
166
 
@@ -173,6 +172,7 @@ module PDD
173
172
  errors.each { |e| PDD.log.error e }
174
173
  PDD.log.error(xml) unless errors.empty?
175
174
  raise SchemaError, errors.join('; ') unless errors.empty?
175
+
176
176
  xml
177
177
  end
178
178
  end