pdd 0.20.7 → 0.20.8

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 8220e74b7aca6e6feddfe8ccad195c1156386ad766ae1dd31311122242c1292c
4
- data.tar.gz: 97816bbe7fdfdb29d595683dd98b6eb41d6bc1880e269c4dc11eee4a2b02f582
3
+ metadata.gz: e3ff37e6f0ac2629e0b31888df2b56f7024512b04b07bd58c29a0525186a40f0
4
+ data.tar.gz: 61076844123f2eda0b32734b2c917ed6eb033956177197626219bda878add5b5
5
5
  SHA512:
6
- metadata.gz: c4128efeb2b302351595bbe99978845e8d8d546f11e341c0d0308821e673f6f2aa2730c4d2170751d711892d17942563275641b8dbec04916d9bb7bac8715f2a
7
- data.tar.gz: 501ddaf76735e9ce50ae3a233c45331d568075979ef3875395e7d361139e008954de9767e14856af7d247adf2a597254b3e89104cc96e5832baa03a278f89a3c
6
+ metadata.gz: e9b46c3238588062c781b1b3705cf343d1ca0e70481b2546ffc407b9a5be6534d45b3c8d0d4ebaab0ac39630ebfc6652feb8c69a8dd8d787bf33eae1a9cb0935
7
+ data.tar.gz: 790b7d73f1b7ea5d2666864b238dfdde211572186d3d84c29a6d6448936b54ef87a567473b83fccfad83ec945ad6f9677a739b0730fde5199618dc79f6cc9ead
data/.gitignore CHANGED
@@ -1,8 +1,8 @@
1
- coverage/
1
+ coverage/**/*
2
2
  Gemfile.lock
3
- .idea/
3
+ .idea/**/*
4
4
  *.gem
5
- .bundle/
5
+ .bundle/**/*
6
6
  .DS_Store
7
- rdoc/
8
- tmp/
7
+ rdoc/**/*
8
+ tmp/**/*
data/.overcommit.yml ADDED
@@ -0,0 +1,96 @@
1
+ # Use this file to configure the Overcommit hooks you wish to use. This will
2
+ # extend the default configuration defined in:
3
+ # https://github.com/sds/overcommit/blob/master/config/default.yml
4
+ #
5
+ # At the topmost level of this YAML file is a key representing type of hook
6
+ # being run (e.g. pre-commit, commit-msg, etc.). Within each type you can
7
+ # customize each hook, such as whether to only run it on certain files (via
8
+ # `include`), whether to only display output if it fails (via `quiet`), etc.
9
+ #
10
+ # For a complete list of hooks, see:
11
+ # https://github.com/sds/overcommit/tree/master/lib/overcommit/hook
12
+ #
13
+ # For a complete list of options that you can use to customize hooks, see:
14
+ # https://github.com/sds/overcommit#configuration
15
+
16
+ PreCommit:
17
+ ALL:
18
+ problem_on_unmodified_line: report
19
+ requires_files: true
20
+ required: false
21
+ quiet: false
22
+
23
+ AuthorEmail:
24
+ enabled: true
25
+ description: 'Check author email'
26
+ requires_files: false
27
+ required: true
28
+ quiet: true
29
+ pattern: '^[^@]+@.*$'
30
+
31
+ AuthorName:
32
+ enabled: true
33
+ description: 'Check for author name'
34
+ requires_files: false
35
+ required: true
36
+ quiet: true
37
+
38
+ BundleCheck:
39
+ enabled: true
40
+ description: 'Check Gemfile dependencies'
41
+ required_executable: 'bundle'
42
+ flags: ['check']
43
+ include:
44
+ - 'Gemfile'
45
+ - 'Gemfile.lock'
46
+ - '*.gemspec'
47
+
48
+ BundleOutdated:
49
+ enabled: true
50
+ description: 'List installed gems with newer versions available'
51
+ required_executable: 'bundle'
52
+ flags: ['outdated', '--strict', '--parseable']
53
+
54
+ RuboCop:
55
+ enabled: true
56
+ description: 'Analyze with RuboCop'
57
+ required_executable: 'bundle'
58
+ flags: ['exec', 'rubocop']
59
+
60
+ # Hooks that are run against every commit message after a user has written it.
61
+ # These hooks are useful for enforcing policies on commit messages written for a
62
+ # project.
63
+ CommitMsg:
64
+ ALL:
65
+ requires_files: false
66
+ quiet: false
67
+
68
+ EmptyMessage:
69
+ enabled: true
70
+ description: 'Check for empty commit message'
71
+ quiet: true
72
+
73
+ MessageFormat:
74
+ enabled: true
75
+ description: 'Check commit message matches expected pattern'
76
+ pattern: '(\[#)(.+)(\]\s)(.+)'
77
+ expected_pattern_message: '[#<Issue Id>] <Commit Message Description>'
78
+ sample_message: '[#167] Refactored onboarding flow'
79
+
80
+ PrePush:
81
+ ALL:
82
+ requires_files: false
83
+ required: false
84
+ quiet: false
85
+
86
+ RakeTarget:
87
+ enabled: true
88
+ quite: true
89
+ description: 'Run rake targets'
90
+ targets:
91
+ - 'rubocop'
92
+ - 'test'
93
+ - 'xcop'
94
+ required_executable: 'bundle'
95
+ flags: ['exec', 'rake']
96
+
data/.pdd CHANGED
@@ -1,9 +1,13 @@
1
1
  --source=.
2
2
  --verbose
3
3
  --skip-errors
4
+ --skip-gitignore
5
+ --include lib/pdd/sources.rb
4
6
  --exclude .idea/**/*
7
+ --exclude .bundle/**/*
5
8
  --exclude target/**/*
6
9
  --exclude coverage/**/*
10
+ --exclude test_assets/**/*
7
11
  --exclude README.md
8
12
  --exclude features/cli.feature
9
13
  --exclude features/parsing.feature
data/.rubocop.yml CHANGED
@@ -16,8 +16,5 @@ Style/MultilineBlockChain:
16
16
  Enabled: false
17
17
  Metrics/BlockLength:
18
18
  Max: 50
19
- # @todo #123:30m Needs to enable this check and fix all issues.
20
- # For now, this is out of the scope of this issue.
21
- # This issue appeared after update TargetRubyVersion to 2.3
22
19
  Style/FrozenStringLiteralComment:
23
20
  Enabled: false
data/.simplecov CHANGED
@@ -18,21 +18,21 @@
18
18
  # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
19
19
  # SOFTWARE.
20
20
 
21
- if Gem.win_platform? then
21
+ if Gem.win_platform?
22
22
  SimpleCov.formatter = SimpleCov::Formatter::MultiFormatter[
23
23
  SimpleCov::Formatter::HTMLFormatter
24
24
  ]
25
25
  SimpleCov.start do
26
- add_filter "/test/"
27
- add_filter "/features/"
26
+ add_filter '/test/'
27
+ add_filter '/features/'
28
28
  end
29
29
  else
30
30
  SimpleCov.formatter = SimpleCov::Formatter::MultiFormatter.new(
31
31
  SimpleCov::Formatter::HTMLFormatter
32
32
  )
33
33
  SimpleCov.start do
34
- add_filter "/test/"
35
- add_filter "/features/"
34
+ add_filter '/test/'
35
+ add_filter '/features/'
36
36
  minimum_coverage 90
37
37
  end
38
38
  end
data/README.md CHANGED
@@ -41,7 +41,13 @@ Run it locally and read its output:
41
41
  $ pdd --help
42
42
  ```
43
43
 
44
- You can exclude certain files from the search, for example:
44
+ ### File and Directory Selection
45
+ You can exclude & include certain number of files from the search via these options:
46
+
47
+ `` --exclude=glob ``
48
+
49
+ You can skip any file(s) with a name suffix that matches the pattern glob, using wildcard matching;
50
+ a name suffix is either the whole path and name, or reg expr, for example:
45
51
 
46
52
  ```bash
47
53
  pdd --exclude=src/**/*.java --exclude=target/**/*
@@ -49,6 +55,17 @@ pdd --exclude=src/**/*.java # exclude .java files in src/
49
55
  pdd --exclude=src/**/* # exclude all files in src/
50
56
  ```
51
57
 
58
+ `` --include=glob ``
59
+
60
+ Search only files whose name matches glob, using wildcard matching as described under ``--exclude``.
61
+ If contradictory ``--include`` and ``--exclude`` options are given, the last matching one wins.
62
+ If no ``--include`` or ``--exclude`` options are given, all files from working directory are included, example:
63
+ аомтрптм
64
+ ```bash
65
+ pdd --include=src/**/*.py # include only .py files in src/
66
+ pdd --include=src/**/* # include all files in src/
67
+ ```
68
+
52
69
  ## How to Format?
53
70
 
54
71
  Every puzzle has to be formatted like this (pay attention
@@ -169,6 +186,7 @@ The XML produced will look approximately like this (here is a
169
186
  </puzzle>
170
187
  </puzzles>
171
188
  ```
189
+ NOTE: puzzles are saved with utf-8 encoding
172
190
 
173
191
  [XSD Schema](http://pdd-xsd.teamed.io/0.19.4.xsd) is here.
174
192
  The most interesting parts of each puzzle are:
@@ -189,15 +207,21 @@ The most interesting parts of each puzzle are:
189
207
  ## How to contribute
190
208
 
191
209
  Read [these guidelines](https://www.yegor256.com/2014/04/15/github-guidelines.html).
192
- Make sure you build is green before you contribute
210
+ Make sure your build is green before you contribute
193
211
  your pull request. You will need to have [Ruby](https://www.ruby-lang.org/en/) 2.3+ and
194
212
  [Bundler](https://bundler.io/) installed. Then:
195
213
 
196
214
  ```
197
- $ bundle update
215
+ $ bundle install --path .bundle
198
216
  $ bundle exec rake
199
217
  ```
200
218
 
219
+ Next, install and run overcommit to install hooks (required once)
220
+ ```
221
+ $ gem install overcommit -v '=0.58.0'
222
+ $ overcommit --install
223
+ ```
224
+
201
225
  If it's clean and you don't see any error messages, submit your pull request.
202
226
 
203
227
  This is how you run the tool locally to test how it works:
data/Rakefile CHANGED
@@ -66,6 +66,12 @@ Xcop::RakeTask.new :xcop do |task|
66
66
  task.excludes = ['target/**/*', 'coverage/**/*']
67
67
  end
68
68
 
69
+ require 'pdd/rake_task'
70
+ desc 'Collecting and parsing all puzzles in project'
71
+ PDD::RakeTask.new :pdd do |task|
72
+ task.includes = ['**/*']
73
+ end
74
+
69
75
  require 'cucumber/rake/task'
70
76
  Cucumber::Rake::Task.new(:features) do |t|
71
77
  t.cucumber_opts = 'features --format progress'
data/bin/pdd CHANGED
@@ -19,7 +19,7 @@
19
19
  # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
20
20
  # SOFTWARE.
21
21
 
22
- STDOUT.sync = true
22
+ $stdout.sync = true
23
23
 
24
24
  require 'shellwords'
25
25
  require 'English'
@@ -42,11 +42,6 @@ begin
42
42
  end
43
43
  args += ARGV
44
44
 
45
- dir = Dir.pwd
46
- @dir = File.absolute_path(dir)
47
- files = Dir.glob(
48
- File.join(@dir, '**/*'), File::FNM_DOTMATCH
49
- ).reject { |f| File.directory?(f) }
50
45
  begin
51
46
  opts = Slop.parse(args, strict: true, help: true) do |o|
52
47
  o.banner = "Usage (#{PDD::VERSION}): pdd [options]"
@@ -65,15 +60,15 @@ begin
65
60
  o.array '-e', '--exclude', 'Glob pattern to exclude, e.g. "**/*.jpg"',
66
61
  default: []
67
62
  o.array '-n', '--include', 'Glob pattern to include, e.g. "**/*.jpg"',
68
- default: [files]
63
+ default: []
69
64
  o.string '-t', '--format', 'Format of the report (xml|html)'
70
65
  o.array(
71
66
  '-r', '--rule', 'Rule to apply (can be used many times)',
72
67
  delimiter: ';'
73
68
  )
74
69
  end
75
- rescue Slop::Error => ex
76
- raise StandardError, "#{ex.message}, try --help"
70
+ rescue Slop::Error => e
71
+ raise StandardError, "#{e.message}, try --help"
77
72
  end
78
73
 
79
74
  if opts.help?
@@ -87,13 +82,17 @@ https://github.com/cqfn/pdd/blob/master/README.md"
87
82
  raise '-f is mandatory when using -v, try --help for more information'
88
83
  end
89
84
 
90
- if opts['skip-gitignore']
91
- raise 'For --skip-gitignore see https://github.com/cqfn/pdd/issues/80'
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)}"
92
91
  end
93
92
 
94
93
  Encoding.default_external = Encoding::UTF_8
95
94
  Encoding.default_internal = Encoding::UTF_8
96
- file = opts.file? ? File.new(opts[:file], 'w') : STDOUT
95
+ file = opts.file? ? File.new(opts[:file], 'w') : $stdout
97
96
  output = PDD::Base.new(opts).xml
98
97
  if opts[:format]
99
98
  if opts[:format] == 'html'
@@ -107,12 +106,12 @@ https://github.com/cqfn/pdd/blob/master/README.md"
107
106
  end
108
107
  end
109
108
  file << output
110
- rescue SystemExit => ex
111
- puts ex.message unless ex.success?
112
- PDD.log.info "Exit code is #{ex.status}"
113
- exit(ex.status)
114
- rescue PDD::Error => ex
115
- puts "#{Rainbow('ERROR').red}: #{ex.message}
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}
116
115
  If you can't understand the cause of this issue or you don't know \
117
116
  how to fix it, please submit a GitHub issue, we will try to help you: \
118
117
  https://github.com/cqfn/pdd/issues. This tool is still in its beta \
@@ -120,8 +119,8 @@ version and we will appreciate your feedback. Here is where you can find \
120
119
  more documentation: https://github.com/cqfn/pdd/blob/master/README.md."
121
120
  PDD.log.info 'Exit code is 1'
122
121
  exit(1)
123
- rescue StandardError => ex
124
- puts "#{Rainbow('ERROR').red} (#{ex.class.name}): #{ex.message}"
122
+ rescue StandardError => e
123
+ puts "#{Rainbow('ERROR').red} (#{e.class.name}): #{e.message}"
125
124
  PDD.log.info 'Exit code is 255'
126
125
  exit(255)
127
126
  end
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"
@@ -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
+
@@ -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
@@ -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?
@@ -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/rake_task.rb CHANGED
@@ -1,16 +1,23 @@
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
@@ -20,11 +27,13 @@ module PDD
20
27
  end
21
28
  end
22
29
 
30
+ private
31
+
23
32
  def run
24
33
  # @todo #125:30m need to implement this method.
25
34
  # For now, it's just a task,
26
- # that accepts one parameter and throws a system error.
27
- abort('NOT IMPLEMENTED')
35
+ # that prints a simple Running pdd... message to user
36
+ puts 'Running pdd...' unless @quiet
28
37
  end
29
38
  end
30
39
  end
@@ -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()')}"
@@ -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
@@ -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
@@ -39,7 +39,7 @@ module PDD
39
39
 
40
40
  def match_markers(line)
41
41
  MARKERS.map do |mkr|
42
- %r{(.*(?:^|\s))#{mkr}\s+#([\w\-\.:/]+)\s+(.+)}.match(line)
42
+ %r{(.*(?:^|\s))#{mkr}\s+#([\w\-.:/]+)\s+(.+)}.match(line)
43
43
  end.compact
44
44
  end
45
45
 
@@ -106,7 +106,7 @@ see https://github.com/cqfn/pdd#how-to-format"
106
106
  # Fetch puzzle
107
107
  def puzzle(lines, match, idx)
108
108
  tail = tail(lines, match[1], idx)
109
- body = (match[3] + ' ' + tail.join(' ')).gsub(/\s+/, ' ').strip
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 \
@@ -148,10 +148,11 @@ against the rules explained here: https://github.com/cqfn/pdd#how-to-format"
148
148
  .map { |t| t[prefix.length, t.length] }
149
149
  .take_while { |t| t =~ /^[ a-zA-Z0-9]/ }
150
150
  .each_with_index do |t, i|
151
- next if t.start_with?(' ')
152
- raise Error, "Space expected at #{start + i + 2}:#{prefix.length}; \
151
+ next if t.start_with?(' ')
152
+
153
+ raise Error, "Space expected at #{start + i + 2}:#{prefix.length}; \
153
154
  make sure all lines in the puzzle body have a single leading space."
154
- end
155
+ end
155
156
  .map { |t| t[1, t.length] }
156
157
  end
157
158
 
@@ -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
@@ -18,54 +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, 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
@@ -76,6 +83,7 @@ module PDD
76
83
  # `test_ignores_binary_files` in `test_sources.rb`.
77
84
  def binary?(file)
78
85
  return false if Gem.win_platform?
86
+
79
87
  `grep -qI '.' #{Shellwords.escape(file)}`
80
88
  if $CHILD_STATUS.success?
81
89
  false
data/lib/pdd/version.rb CHANGED
@@ -23,5 +23,5 @@
23
23
  # Copyright:: Copyright (c) 2014-2021 Yegor Bugayenko
24
24
  # License:: MIT
25
25
  module PDD
26
- VERSION = '0.20.7'.freeze
26
+ VERSION = '0.20.8'.freeze
27
27
  end
data/lib/pdd.rb CHANGED
@@ -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
data/pdd.gemspec CHANGED
@@ -20,17 +20,18 @@
20
20
 
21
21
  require 'English'
22
22
 
23
- lib = File.expand_path('../lib', __FILE__)
23
+ lib = File.expand_path('lib', __dir__)
24
24
  $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
25
25
  require_relative 'lib/pdd/version'
26
26
 
27
27
  Gem::Specification.new do |s|
28
28
  s.specification_version = 2 if s.respond_to? :specification_version=
29
29
  if s.respond_to? :required_rubygems_version=
30
- s.required_rubygems_version = Gem::Requirement.new('>= 0')
30
+ s.required_rubygems_version =
31
+ Gem::Requirement.new('>= 0')
31
32
  end
32
33
  s.rubygems_version = '2.3'
33
- s.required_ruby_version = '>= 2.3'
34
+ s.required_ruby_version = '~> 2.3'
34
35
  s.name = 'pdd'
35
36
  s.version = PDD::VERSION
36
37
  s.license = 'MIT'
@@ -56,5 +57,6 @@ Gem::Specification.new do |s|
56
57
  s.add_development_dependency 'rspec-rails', '3.1.0'
57
58
  s.add_development_dependency 'rubocop', '0.52.1'
58
59
  s.add_development_dependency 'rubocop-rspec', '1.15.1'
60
+ s.add_development_dependency 'slop', '4.9.1'
59
61
  s.add_development_dependency 'xcop', '0.5.8'
60
62
  end
data/test/test__helper.rb CHANGED
@@ -18,7 +18,7 @@
18
18
  # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
19
19
  # SOFTWARE.
20
20
 
21
- STDOUT.sync = true
21
+ $stdout.sync = true
22
22
 
23
23
  require 'simplecov'
24
24
  SimpleCov.start
data/test/test_pdd.rb CHANGED
@@ -75,6 +75,7 @@ class TestPDD < Minitest::Test
75
75
  git add -f .
76
76
  git commit --quiet -am 'first version'
77
77
  ")
78
+
78
79
  matches(
79
80
  Nokogiri::XML(PDD::Base.new(opts).xml),
80
81
  [
@@ -5,11 +5,14 @@ require_relative '../lib/pdd/rake_task'
5
5
 
6
6
  # Test for RakeTask
7
7
  class TestRakeTask < Minitest::Test
8
- def test_base
9
- PDD::RakeTask.new(:pdd1)
10
- error = assert_raises SystemExit do
8
+ def test_basic
9
+ Dir.mktmpdir 'test' do |dir|
10
+ file = File.join(dir, 'a.txt')
11
+ File.write(file, "\x40todo #55 hello!")
12
+ PDD::RakeTask.new(:pdd1) do |task|
13
+ task.quiet = true
14
+ end
11
15
  Rake::Task['pdd1'].invoke
12
16
  end
13
- assert_equal('NOT IMPLEMENTED', error.message)
14
17
  end
15
18
  end
data/test/test_source.rb CHANGED
@@ -122,6 +122,25 @@ class TestSource < Minitest::Test
122
122
  end
123
123
  end
124
124
 
125
+ def test_succeed_utf8_encoded_body
126
+ Dir.mktmpdir 'test' do |dir|
127
+ file = File.join(dir, 'a.txt')
128
+ File.write(
129
+ file,
130
+ "
131
+ * \x40todo #44 Привет, мир, мне кофе
132
+ * вторая линия
133
+ "
134
+ )
135
+ list = PDD::VerboseSource.new(file, PDD::Source.new(file, 'hey')).puzzles
136
+ assert_equal 1, list.size
137
+ puzzle = list.first
138
+ assert_equal '2-3', puzzle.props[:lines]
139
+ assert_equal 'Привет, мир, мне кофе вторая линия', puzzle.props[:body]
140
+ assert_equal '44', puzzle.props[:ticket]
141
+ end
142
+ end
143
+
125
144
  def test_failing_on_incomplete_puzzle
126
145
  Dir.mktmpdir 't5' do |dir|
127
146
  file = File.join(dir, 'ff.txt')
@@ -141,7 +160,7 @@ class TestSource < Minitest::Test
141
160
  def test_failing_on_broken_unicode
142
161
  Dir.mktmpdir 'test' do |dir|
143
162
  file = File.join(dir, 'xx.txt')
144
- File.write(file, ' * \x40todo #44 this is a broken unicode: ' + 0x92.chr)
163
+ File.write(file, " * \\x40todo #44 this is a broken unicode: #{0x92.chr}")
145
164
  assert_raises PDD::Error do
146
165
  stub_source_find_github_user(file, 'xx', &:puzzles)
147
166
  end
@@ -209,6 +228,7 @@ class TestSource < Minitest::Test
209
228
  git add a.txt
210
229
  git commit --quiet -am 'first version'
211
230
  ")
231
+
212
232
  stub_source_find_github_user(File.join(dir, 'a.txt')) do |source|
213
233
  list = source.puzzles
214
234
  assert_equal 1, list.size
@@ -237,6 +257,7 @@ class TestSource < Minitest::Test
237
257
  git add a.txt
238
258
  git commit --quiet -am 'first version'
239
259
  ")
260
+
240
261
  stub_source_find_github_user(File.join(dir, 'a.txt')) do |source|
241
262
  list = source.puzzles
242
263
  assert_equal 1, list.size
@@ -264,6 +285,7 @@ class TestSource < Minitest::Test
264
285
  git add a.txt
265
286
  git commit --quiet -am 'first version'
266
287
  ")
288
+
267
289
  stub_source_find_github_user(File.join(dir, 'a.txt')) do |source|
268
290
  list = source.puzzles
269
291
  assert_equal 1, list.size
@@ -286,6 +308,7 @@ class TestSource < Minitest::Test
286
308
  git commit --quiet -am 'first version'
287
309
  echo '\x40todo #1 this is a puzzle uncommitted' > a.txt
288
310
  ")
311
+
289
312
  stub_source_find_github_user(File.join(dir, 'a.txt')) do |source|
290
313
  list = source.puzzles
291
314
  assert_equal 1, list.size
@@ -73,6 +73,18 @@ class TestSourceTodo < Minitest::Test
73
73
  )
74
74
  end
75
75
 
76
+ def test_todo_utf8_encoded_body
77
+ check_valid_puzzle(
78
+ "
79
+ // TODO #45 Привет, мир, мне кофе
80
+ // вторая линия
81
+ ",
82
+ '2-3',
83
+ 'Привет, мир, мне кофе вторая линия',
84
+ '45'
85
+ )
86
+ end
87
+
76
88
  def test_todo_colon_parsing
77
89
  check_valid_puzzle(
78
90
  "
data/test/test_sources.rb CHANGED
@@ -92,14 +92,16 @@ class TestSources < Minitest::Test
92
92
  def test_includes_by_pattern
93
93
  in_temp(['a/first.txt', 'b/c/d/second.txt']) do |dir|
94
94
  list = PDD::Sources.new(dir).include('b/c/d/second.txt').fetch
95
- assert_equal 1, list.size
95
+ assert_equal 2, list.size
96
96
  end
97
97
  end
98
98
 
99
99
  def test_includes_recursively
100
100
  in_temp(['a/first.txt', 'b/c/second.txt', 'b/c/d/third.txt']) do |dir|
101
- list = PDD::Sources.new(dir).include('**/*').fetch
102
- assert_equal 0, list.size
101
+ sources = PDD::Sources.new(dir).exclude('b/c/**')
102
+ sources.include('b/c/d/third.txt')
103
+ list = sources.fetch
104
+ assert_equal 2, list.size
103
105
  end
104
106
  end
105
107
 
data/utils/glob.rb ADDED
@@ -0,0 +1,67 @@
1
+ # Copyright (c) 2014-2021 Yegor Bugayenko
2
+ #
3
+ # Permission is hereby granted, free of charge, to any person obtaining a copy
4
+ # of this software and associated documentation files (the 'Software'), to deal
5
+ # in the Software without restriction, including without limitation the rights
6
+ # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7
+ # copies of the Software, and to permit persons to whom the Software is
8
+ # furnished to do so, subject to the following conditions:
9
+ #
10
+ # The above copyright notice and this permission notice shall be included in all
11
+ # copies or substantial portions of the Software.
12
+ #
13
+ # THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14
+ # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15
+ # FITNESS FOR A PARTICULAR PURPOSE AND NONINFINGEMENT. IN NO EVENT SHALL THE
16
+ # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17
+ # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18
+ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
19
+ # SOFTWARE.
20
+
21
+ # Utility glob class
22
+ class Glob
23
+ NO_LEADING_DOT = '(?=[^\.])'.freeze
24
+
25
+ def initialize(glob_string)
26
+ @glob_string = glob_string
27
+ end
28
+
29
+ # rubocop:disable Metrics/CyclomaticComplexity
30
+ # rubocop:disable Metrics/MethodLength
31
+ def to_regexp
32
+ chars = @glob_string.gsub(%r{(\*\*\/\*)|(\*\*)}, '*').split('')
33
+ in_curlies = 0, escaping = false
34
+ chars.map do |char|
35
+ if escaping
36
+ escaping = false
37
+ return char
38
+ end
39
+ case char
40
+ when '*'
41
+ '.*'
42
+ when '?'
43
+ '.'
44
+ when '.'
45
+ '\\.'
46
+ when '{'
47
+ in_curlies += 1
48
+ '('
49
+ when '}'
50
+ if in_curlies.positive?
51
+ in_curlies -= 1
52
+ return ')'
53
+ end
54
+ return char
55
+ when ','
56
+ in_curlies.positive? ? '|' : char
57
+ when '\\'
58
+ escaping = true
59
+ '\\'
60
+ else
61
+ char
62
+ end
63
+ end.join
64
+ end
65
+ # rubocop:enable Metrics/CyclomaticComplexity
66
+ # rubocop:enable Metrics/MethodLength
67
+ end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: pdd
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.20.7
4
+ version: 0.20.8
5
5
  platform: ruby
6
6
  authors:
7
7
  - Yegor Bugayenko
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2021-08-24 00:00:00.000000000 Z
11
+ date: 2021-12-17 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: nokogiri
@@ -178,6 +178,20 @@ dependencies:
178
178
  - - '='
179
179
  - !ruby/object:Gem::Version
180
180
  version: 1.15.1
181
+ - !ruby/object:Gem::Dependency
182
+ name: slop
183
+ requirement: !ruby/object:Gem::Requirement
184
+ requirements:
185
+ - - '='
186
+ - !ruby/object:Gem::Version
187
+ version: 4.9.1
188
+ type: :development
189
+ prerelease: false
190
+ version_requirements: !ruby/object:Gem::Requirement
191
+ requirements:
192
+ - - '='
193
+ - !ruby/object:Gem::Version
194
+ version: 4.9.1
181
195
  - !ruby/object:Gem::Dependency
182
196
  name: xcop
183
197
  requirement: !ruby/object:Gem::Requirement
@@ -206,6 +220,7 @@ files:
206
220
  - ".github/ISSUE_TEMPLATE.md"
207
221
  - ".github/PULL_REQUEST_TEMPLATE.md"
208
222
  - ".gitignore"
223
+ - ".overcommit.yml"
209
224
  - ".pdd"
210
225
  - ".rubocop.yml"
211
226
  - ".rultor.yml"
@@ -258,6 +273,7 @@ files:
258
273
  - test_assets/cambria.woff
259
274
  - test_assets/elegant-objects.png
260
275
  - test_assets/favicon.ico
276
+ - utils/glob.rb
261
277
  homepage: http://github.com/cqfn/pdd
262
278
  licenses:
263
279
  - MIT
@@ -269,7 +285,7 @@ require_paths:
269
285
  - lib
270
286
  required_ruby_version: !ruby/object:Gem::Requirement
271
287
  requirements:
272
- - - ">="
288
+ - - "~>"
273
289
  - !ruby/object:Gem::Version
274
290
  version: '2.3'
275
291
  required_rubygems_version: !ruby/object:Gem::Requirement