pdd 0.20.3 → 0.20.4

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
- SHA1:
3
- metadata.gz: 923491c89e00bd204e14a12c1c3d2f792c44c14b
4
- data.tar.gz: 70ed765544c00e134373e5db4a4801afd4c5de53
2
+ SHA256:
3
+ metadata.gz: cc5c52ddb7620147399b0ed8cec3d13900d1d36e695c42021cf2e887b4b80d7c
4
+ data.tar.gz: 6da8a3a29675c6359e427d2c16048f32a032fcc25247447ce110ac3e649b147b
5
5
  SHA512:
6
- metadata.gz: a4b425410f882fcf7400ede903ba3b9d6ed983d96dc91c58e6d70c0d911202f37db1b40657b7115a50e5afd081917882ca54df65656f020f792f91c33ce8aea5
7
- data.tar.gz: 3c02e5936c0538054a83a408b0f9083d8f45fbeabe0712f99c368b7455d3d5a31571a043623342ec85275353c6b37df136af2c5313dbbc102ea13fee4c347520
6
+ metadata.gz: e986672fe0a226d5577bd35d296f9d55685d0d77384e516d535b5cdd672a43c8e4d33a9167b6cf9e0397738b4dc7967a904324f5e77c6c48413bc4663d2e61c3
7
+ data.tar.gz: c0086dc48c63539c09ea70bcea1d7fba64c1eb48fc51a6a2ffa65740e0333a38b6bb93b7dfe634b0e4e99d173539b19054922d2cbfebc78a88dc32a42f41bb36
@@ -2,6 +2,8 @@ assets:
2
2
  rubygems.yml: zerocracy/home#assets/rubygems.yml
3
3
  s3cfg: zerocracy/home#assets/s3cfg
4
4
  install: |
5
+ export GEM_HOME=~/.ruby
6
+ export GEM_PATH=$GEM_HOME:$GEM_PATH
5
7
  sudo gem install pdd
6
8
  sudo gem install xcop
7
9
  release:
data/README.md CHANGED
@@ -1,5 +1,7 @@
1
1
  <img src="https://avatars2.githubusercontent.com/u/24456188" width="64px" height="64px"/>
2
2
 
3
+ [![Donate via Zerocracy](https://www.0crat.com/contrib-badge/C3T46CUJJ.svg)](https://www.0crat.com/contrib/C3T46CUJJ)
4
+
3
5
  [![EO principles respected here](http://www.elegantobjects.org/badge.svg)](http://www.elegantobjects.org)
4
6
  [![Managed by Zerocracy](https://www.0crat.com/badge/C3T46CUJJ.svg)](https://www.0crat.com/p/C3T46CUJJ)
5
7
  [![DevOps By Rultor.com](http://www.rultor.com/b/yegor256/pdd)](http://www.rultor.com/p/yegor256/pdd)
@@ -13,6 +15,7 @@
13
15
  [![Dependency Status](https://gemnasium.com/yegor256/pdd.svg)](https://gemnasium.com/yegor256/pdd)
14
16
  [![Maintainability](https://api.codeclimate.com/v1/badges/c8e46256fdd8ddc817e5/maintainability)](https://codeclimate.com/github/yegor256/pdd/maintainability)
15
17
  [![Test Coverage](https://img.shields.io/codecov/c/github/yegor256/pdd.svg)](https://codecov.io/github/yegor256/pdd?branch=master)
18
+ [![Yard Docs](http://img.shields.io/badge/yard-docs-blue.svg)](http://rubydoc.info/github/yegor256/pdd/master/frames)
16
19
 
17
20
  ## What This is for?
18
21
 
@@ -53,6 +56,16 @@ pdd --exclude=src/**/* # exclude all files in src/
53
56
  Every puzzle has to be formatted like this (pay attention
54
57
  to the leading space in every consecutive line):
55
58
 
59
+ ```java
60
+ /**
61
+ * @todo #[issue#]<:[time]></[role]> <[description]>
62
+ */
63
+ [related code]
64
+ ```
65
+ \[\] - Replace with apropriate data (see text enclosed in brackets)
66
+ <> - Omitable (enclosed data can be left out)
67
+
68
+ Example:
56
69
  ```java
57
70
  /**
58
71
  * @todo #234:15m/DEV This is something to do later
@@ -168,9 +181,19 @@ The most interesting parts of each puzzle are:
168
181
 
169
182
  * `lines` is where the puzzle is found, inside the file.
170
183
 
171
- ## How to contribute?
184
+ # How to contribute
185
+
186
+ Read [these guidelines](https://www.yegor256.com/2014/04/15/github-guidelines.html).
187
+ Make sure you build is green before you contribute
188
+ your pull request. You will need to have [Ruby](https://www.ruby-lang.org/en/) 2.3+ and
189
+ [Bundler](https://bundler.io/) installed. Then:
190
+
191
+ ```
192
+ $ bundle update
193
+ $ rake
194
+ ```
172
195
 
173
- Just submit a pull request. Make sure `rake` passes.
196
+ If it's clean and you don't see any error messages, submit your pull request.
174
197
 
175
198
  This is how you run the tool locally to test how it works:
176
199
 
data/bin/pdd CHANGED
@@ -100,7 +100,7 @@ rescue SystemExit => ex
100
100
  PDD.log.info "Exit code is #{ex.status}"
101
101
  exit(ex.status)
102
102
  rescue PDD::Error => ex
103
- puts "#{Rainbow('ERROR').red}: #{ex.message}. \
103
+ puts "#{Rainbow('ERROR').red}: #{ex.message}
104
104
  If you can't understand the cause of this issue or you don't know \
105
105
  how to fix it, please submit a GitHub issue, we will try to help you: \
106
106
  https://github.com/yegor256/pdd/issues. This tool is still in its beta \
@@ -19,6 +19,8 @@
19
19
  # SOFTWARE.
20
20
 
21
21
  require 'digest/md5'
22
+ require 'net/http'
23
+ require 'json'
22
24
  require 'shellwords'
23
25
  require_relative '../pdd'
24
26
  require_relative '../pdd/puzzle'
@@ -41,23 +43,13 @@ module PDD
41
43
  lines = File.readlines(@file, encoding: 'UTF-8')
42
44
  lines.each_with_index do |line, idx|
43
45
  begin
44
- /[^\s]\x40todo/.match(line) do |_|
45
- raise Error, "\x40todo must have a leading space to become \
46
- a puzzle, as this page explains: https://github.com/yegor256/pdd#how-to-format"
47
- end
48
- /\x40todo(?!\s+#)/.match(line) do |_|
49
- raise Error, "\x40todo found, but puzzle can't be parsed, \
50
- most probably because \x40todo is not followed by a puzzle marker, \
51
- as this page explains: https://github.com/yegor256/pdd#how-to-format"
52
- end
53
- /\x40todo\s+#\s/.match(line) do |_|
54
- raise Error, "\x40todo found, but there is an unexpected space \
55
- after the hash sign, it should not be there, \
56
- see https://github.com/yegor256/pdd#how-to-format"
57
- end
46
+ check_rules(line)
58
47
  %r{(.*(?:^|\s))\x40todo\s+#([\w\-\.:/]+)\s+(.+)}.match(line) do |m|
59
48
  puzzles << puzzle(lines.drop(idx + 1), m, idx)
60
49
  end
50
+ %r{(.*(?:^|\s))TODO:?\s+#([\w\-\.:/]+)\s+(.+)}.match(line) do |m|
51
+ puzzles << puzzle(lines.drop(idx + 1), m, idx)
52
+ end
61
53
  rescue Error, ArgumentError => ex
62
54
  raise Error, "puzzle at line ##{idx + 1}; #{ex.message}"
63
55
  end
@@ -67,10 +59,49 @@ see https://github.com/yegor256/pdd#how-to-format"
67
59
 
68
60
  private
69
61
 
62
+ def get_no_leading_space_error(todo)
63
+ "#{todo} must have a leading space to become \
64
+ a puzzle, as this page explains: https://github.com/yegor256/pdd#how-to-format"
65
+ end
66
+
67
+ def get_no_puzzle_marker_error(todo)
68
+ "#{todo} found, but puzzle can't be parsed, \
69
+ most probably because #{todo} is not followed by a puzzle marker, \
70
+ as this page explains: https://github.com/yegor256/pdd#how-to-format"
71
+ end
72
+
73
+ def get_space_after_hash_error(todo)
74
+ "#{todo} found, but there is an unexpected space \
75
+ after the hash sign, it should not be there, \
76
+ see https://github.com/yegor256/pdd#how-to-format"
77
+ end
78
+
79
+ def check_rules(line)
80
+ /[^\s]\x40todo/.match(line) do |_|
81
+ raise Error, get_no_leading_space_error("\x40todo")
82
+ end
83
+ /\x40todo(?!\s+#)/.match(line) do |_|
84
+ raise Error, get_no_puzzle_marker_error("\x40todo")
85
+ end
86
+ /\x40todo\s+#\s/.match(line) do |_|
87
+ raise Error, get_space_after_hash_error("\x40todo")
88
+ end
89
+ /[^\s]TODO:?/.match(line) do |_|
90
+ raise Error, get_no_leading_space_error('TODO')
91
+ end
92
+ /TODO(?!:?\s+#)/.match(line) do |_|
93
+ raise Error, get_no_puzzle_marker_error('TODO')
94
+ end
95
+ /TODO:?\s+#\s/.match(line) do |_|
96
+ raise Error, get_space_after_hash_error('TODO')
97
+ end
98
+ end
99
+
70
100
  # Fetch puzzle
71
101
  def puzzle(lines, match, idx)
72
102
  tail = tail(lines, match[1], idx)
73
103
  body = (match[3] + ' ' + tail.join(' ')).gsub(/\s+/, ' ').strip
104
+ body = body.chomp('*/-->').strip
74
105
  marker = marker(match[2])
75
106
  Puzzle.new(
76
107
  marker.merge(
@@ -136,7 +167,7 @@ at position ##{prefix.length + 1}."
136
167
  git = "cd #{dir} && git"
137
168
  if `#{git} rev-parse --is-inside-work-tree 2>/dev/null`.strip == 'true'
138
169
  cmd = "#{git} blame -L #{pos},#{pos} --porcelain #{name}"
139
- Hash[
170
+ add_github_login(Hash[
140
171
  `#{cmd}`.split("\n").map do |line|
141
172
  if line =~ /^author /
142
173
  [:author, line.sub(/^author /, '')]
@@ -151,11 +182,41 @@ at position ##{prefix.length + 1}."
151
182
  ]
152
183
  end
153
184
  end.compact
154
- ]
185
+ ])
155
186
  else
156
187
  {}
157
188
  end
158
189
  end
190
+
191
+ def add_github_login(info)
192
+ login = find_github_login(info[:email])
193
+ info[:author] = "@#{login}" unless login.empty?
194
+ info
195
+ end
196
+
197
+ def get_json(query)
198
+ uri = URI.parse(query)
199
+ http = Net::HTTP.new(uri.hostname, uri.port)
200
+ http.use_ssl = uri.scheme == 'https'
201
+ req = Net::HTTP::Get.new(uri.request_uri)
202
+ req.set_content_type('application/json')
203
+ res = http.request(req)
204
+ JSON.parse res.body
205
+ end
206
+
207
+ def find_github_user(email)
208
+ base_uri = 'https://api.github.com/search/users'
209
+ query = base_uri + "?q=#{email}+in:email&perpage=1"
210
+ json = get_json query
211
+ json['items'].first
212
+ end
213
+
214
+ def find_github_login(email)
215
+ user = find_github_user email
216
+ user['login']
217
+ rescue StandardError
218
+ ''
219
+ end
159
220
  end
160
221
 
161
222
  # Verbose Source.
@@ -57,13 +57,17 @@ module PDD
57
57
 
58
58
  private
59
59
 
60
- def binary?(f)
60
+ # @todo #98:30min Change the implementation of this method
61
+ # to also work in Windows machines. Investigate the possibility
62
+ # of use a gem for this. After that, remove the skip of the test
63
+ # `test_ignores_binary_files` in `test_sources.rb`.
64
+ def binary?(file)
61
65
  return false if Gem.win_platform?
62
- `grep -qI '.' #{Shellwords.escape(f)}`
66
+ `grep -qI '.' #{Shellwords.escape(file)}`
63
67
  if $CHILD_STATUS.success?
64
68
  false
65
69
  else
66
- PDD.log.info "#{f} is a binary file (#{File.size(f)} bytes)"
70
+ PDD.log.info "#{file} is a binary file (#{File.size(file)} bytes)"
67
71
  true
68
72
  end
69
73
  end
@@ -23,5 +23,5 @@
23
23
  # Copyright:: Copyright (c) 2014-2018 Yegor Bugayenko
24
24
  # License:: MIT
25
25
  module PDD
26
- VERSION = '0.20.3'.freeze
26
+ VERSION = '0.20.4'.freeze
27
27
  end
@@ -28,16 +28,14 @@ require_relative '../lib/pdd/sources'
28
28
  # Copyright:: Copyright (c) 2014-2018 Yegor Bugayenko
29
29
  # License:: MIT
30
30
  class TestSource < Minitest::Test
31
- # @todo #85:30min Make this test pass on AppVeyor.
32
31
  def test_parsing
33
- skip('Skipped because it fails on AppVeyor')
34
32
  Dir.mktmpdir 'test' do |dir|
35
33
  file = File.join(dir, 'a.txt')
36
34
  File.write(
37
35
  file,
38
36
  "
39
37
  * \x40todo #44 привет,
40
- * how are you\t\r\tdoing?
38
+ * how are you\t\tdoing?
41
39
  * -something else
42
40
  Something else
43
41
  ~~ \x40todo #ABC-3 this is another puzzle
@@ -90,7 +88,6 @@ class TestSource < Minitest::Test
90
88
  end
91
89
 
92
90
  def test_failing_on_broken_unicode
93
- skip if Gem.win_platform?
94
91
  Dir.mktmpdir 'test' do |dir|
95
92
  file = File.join(dir, 'xx.txt')
96
93
  File.write(file, ' * \x40todo #44 this is a broken unicode: ' + 0x92.chr)
@@ -101,7 +98,6 @@ class TestSource < Minitest::Test
101
98
  end
102
99
 
103
100
  def test_failing_on_invalid_puzzle_without_hash_sign
104
- skip('doesnt work now')
105
101
  Dir.mktmpdir 'test' do |dir|
106
102
  file = File.join(dir, 'a.txt')
107
103
  File.write(
@@ -113,7 +109,7 @@ class TestSource < Minitest::Test
113
109
  error = assert_raises PDD::Error do
114
110
  PDD::VerboseSource.new(file, PDD::Source.new(file, 'hey')).puzzles
115
111
  end
116
- assert !error.message.index('Incorrect format').nil?
112
+ assert !error.message.index('is not followed by a puzzle marker').nil?
117
113
  end
118
114
  end
119
115
 
@@ -198,4 +194,39 @@ class TestSource < Minitest::Test
198
194
  assert_match(/\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}Z/, puzzle.props[:time])
199
195
  end
200
196
  end
197
+
198
+ def test_uses_github_login
199
+ skip if Gem.win_platform?
200
+ Dir.mktmpdir 'test' do |dir|
201
+ raise unless system("
202
+ cd '#{dir}'
203
+ git init --quiet .
204
+ git config user.email yegor256@gmail.com
205
+ git config user.name test
206
+ echo '\x40todo #1 this is the puzzle' > a.txt
207
+ git add a.txt
208
+ git commit --quiet -am 'first version'
209
+ ")
210
+ list = PDD::Source.new(File.join(dir, 'a.txt'), '').puzzles
211
+ assert_equal 1, list.size
212
+ puzzle = list.first
213
+ assert_equal '@yegor256', puzzle.props[:author]
214
+ end
215
+ end
216
+
217
+ def test_skips_thymeleaf_close_tag
218
+ Dir.mktmpdir 'test' do |dir|
219
+ file = File.join(dir, 'a.txt')
220
+ File.write(
221
+ file,
222
+ '<!--/* @todo #123 puzzle info */-->'
223
+ )
224
+ list = PDD::VerboseSource.new(file, PDD::Source.new(file, 'hey')).puzzles
225
+ assert_equal 1, list.size
226
+ puzzle = list.first
227
+ assert_equal '1-1', puzzle.props[:lines]
228
+ assert_equal 'puzzle info', puzzle.props[:body]
229
+ assert_equal '123', puzzle.props[:ticket]
230
+ end
231
+ end
201
232
  end
@@ -0,0 +1,168 @@
1
+ # Copyright (c) 2014-2018 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
+ require 'minitest/autorun'
22
+ require 'tmpdir'
23
+ require_relative '../lib/pdd'
24
+ require_relative '../lib/pdd/sources'
25
+
26
+ class TestSourceTodo < Minitest::Test
27
+ def check_valid_puzzle(text, lines, body, ticket)
28
+ Dir.mktmpdir 'test' do |dir|
29
+ file = File.join(dir, 'a.txt')
30
+ File.write(file, text)
31
+ list = PDD::VerboseSource.new(file, PDD::Source.new(file, 'hey')).puzzles
32
+ assert_equal 1, list.size
33
+ puzzle = list.first
34
+ assert_equal lines, puzzle.props[:lines]
35
+ assert_equal body, puzzle.props[:body]
36
+ assert_equal ticket, puzzle.props[:ticket]
37
+ end
38
+ end
39
+
40
+ def check_invalid_puzzle(text, error_msg)
41
+ Dir.mktmpdir 'test' do |dir|
42
+ file = File.join(dir, 'a.txt')
43
+ File.write(file, text)
44
+ error = assert_raises PDD::Error do
45
+ PDD::VerboseSource.new(file, PDD::Source.new(file, 'hey')).puzzles
46
+ end
47
+ assert !error.message.index(error_msg).nil?
48
+ end
49
+ end
50
+
51
+ def test_todo_parsing
52
+ check_valid_puzzle(
53
+ "
54
+ // TODO #45 task description
55
+ ",
56
+ '2-2',
57
+ 'task description',
58
+ '45'
59
+ )
60
+ end
61
+
62
+ def test_todo_parsing_multi_line
63
+ check_valid_puzzle(
64
+ "
65
+ // TODO #45 task description
66
+ // second line
67
+ ",
68
+ '2-3',
69
+ 'task description second line',
70
+ '45'
71
+ )
72
+ end
73
+
74
+ def test_todo_colon_parsing
75
+ check_valid_puzzle(
76
+ "
77
+ // TODO: #45 task description
78
+ ",
79
+ '2-2',
80
+ 'task description',
81
+ '45'
82
+ )
83
+ end
84
+
85
+ def test_todo_colon_parsing_multi_line
86
+ check_valid_puzzle(
87
+ "
88
+ // TODO: #45 task description
89
+ // second line
90
+ ",
91
+ '2-3',
92
+ 'task description second line',
93
+ '45'
94
+ )
95
+ end
96
+
97
+ def test_todo_failing_no_space_on_second_line
98
+ check_invalid_puzzle(
99
+ "
100
+ * TODO #45 this puzzle
101
+ * has not space on second line",
102
+ 'Space expected'
103
+ )
104
+ end
105
+
106
+ def test_todo_colon_failing_no_space_on_second_line
107
+ check_invalid_puzzle(
108
+ "
109
+ * TODO: #45 this puzzle
110
+ * has not space on second line",
111
+ 'Space expected'
112
+ )
113
+ end
114
+
115
+ def test_todo_failing_no_ticket
116
+ check_invalid_puzzle(
117
+ "
118
+ * TODO this puzzle misses ticket name/number
119
+ ",
120
+ 'TODO is not followed by'
121
+ )
122
+ end
123
+
124
+ def test_todo_colon_failing_no_ticket
125
+ check_invalid_puzzle(
126
+ "
127
+ * TODO: this puzzle misses ticket name/number
128
+ ",
129
+ 'TODO is not followed by'
130
+ )
131
+ end
132
+
133
+ def test_todo_failing_space_after_hash
134
+ check_invalid_puzzle(
135
+ "
136
+ * TODO # 45 this puzzle has space after hash
137
+ ",
138
+ 'TODO found, but there is an unexpected space after the hash sign'
139
+ )
140
+ end
141
+
142
+ def test_todo_colon_failing_space_after_hash
143
+ check_invalid_puzzle(
144
+ "
145
+ * TODO: # 45 this puzzle has space after hash
146
+ ",
147
+ 'TODO found, but there is an unexpected space after the hash sign'
148
+ )
149
+ end
150
+
151
+ def test_todo_failing_no_space_before
152
+ check_invalid_puzzle(
153
+ "
154
+ *TODO #45 this puzzle has no space before todo
155
+ ",
156
+ 'TODO must have a leading space'
157
+ )
158
+ end
159
+
160
+ def test_todo_colon_failing_no_space_before
161
+ check_invalid_puzzle(
162
+ "
163
+ *TODO: #45 this puzzle has no space before todo
164
+ ",
165
+ 'TODO must have a leading space'
166
+ )
167
+ end
168
+ end
@@ -82,6 +82,13 @@ class TestSources < Minitest::Test
82
82
  end
83
83
  end
84
84
 
85
+ def test_excludes_recursively
86
+ in_temp(['a/first.txt', 'b/c/second.txt', 'b/c/d/third.txt']) do |dir|
87
+ list = PDD::Sources.new(dir).exclude('**/*').fetch
88
+ assert_equal 0, list.size
89
+ end
90
+ end
91
+
85
92
  def test_fails_with_verbose_output
86
93
  in_temp do |dir|
87
94
  File.write(File.join(dir, 'z1.txt'), "\x40todobroken\n")
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.3
4
+ version: 0.20.4
5
5
  platform: ruby
6
6
  authors:
7
7
  - Yegor Bugayenko
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2018-03-05 00:00:00.000000000 Z
11
+ date: 2018-12-25 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: nokogiri
@@ -233,6 +233,7 @@ files:
233
233
  - test/test_pdd.rb
234
234
  - test/test_roles.rb
235
235
  - test/test_source.rb
236
+ - test/test_source_todo.rb
236
237
  - test/test_sources.rb
237
238
  - test/test_text.rb
238
239
  - test_assets/aladdin.jpg
@@ -261,7 +262,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
261
262
  version: '0'
262
263
  requirements: []
263
264
  rubyforge_project:
264
- rubygems_version: 2.6.8
265
+ rubygems_version: 2.7.6
265
266
  signing_key:
266
267
  specification_version: 2
267
268
  summary: Puzzle Driven Development collector
@@ -283,5 +284,6 @@ test_files:
283
284
  - test/test_pdd.rb
284
285
  - test/test_roles.rb
285
286
  - test/test_source.rb
287
+ - test/test_source_todo.rb
286
288
  - test/test_sources.rb
287
289
  - test/test_text.rb