diff --git a/.github/workflows/codecov.yml b/.github/workflows/codecov.yml index f85e212..5b3395c 100644 --- a/.github/workflows/codecov.yml +++ b/.github/workflows/codecov.yml @@ -17,7 +17,7 @@ jobs: ruby-version: '2.7' - run: bundle update - run: bundle exec rake - - uses: codecov/codecov-action@v3 + - uses: codecov/codecov-action@v4 with: token: ${{ secrets.CODECOV_TOKEN }} fail_ci_if_error: true diff --git a/Gemfile b/Gemfile index c5fed8b..3ed1bba 100644 --- a/Gemfile +++ b/Gemfile @@ -27,7 +27,7 @@ gem 'minitest', '5.16.2', require: false gem 'rake', '13.0.6', require: false gem 'rdoc', '6.4.0', require: false gem 'rspec-rails', '5.1.2', require: false -gem 'rubocop', '1.53.1', require: false +gem 'rubocop', '1.60.2', require: false gem 'rubocop-rspec', '2.22.0', require: false gem 'simplecov', '0.22.0', require: false gem 'simplecov-cobertura', '~> 2.1' diff --git a/README.md b/README.md index 27289f4..fb5f069 100644 --- a/README.md +++ b/README.md @@ -116,6 +116,10 @@ Example: * @todo #234:15m/DEV This is something to do later * in one of the next releases. I can't figure out * how to implement it now, that's why the puzzle. + * The text can be so long, as needed, just use + * the same anount of spaces, as the second line. + * This text will be not a part of the puzzle, as + * it has less spaces. */ void sendEmail() { throw new UnsupportedOperationException(); @@ -142,18 +146,16 @@ as long as you have one of the 3 supported keywords right in front of the mandatory marker): ``` -// @todo #224 -/* @todo #TEST-13 */ -# @todo #55:45min -@todo #67/DES -;; @todo #678:40m/DEV -// TODO: #1:30min -(* TODO #42 *) +// @todo #224 Puzzle description +# @todo #55:45min Puzzle description +@todo #67/DES Puzzle description +;; @todo #678:40m/DEV Puzzle description +// TODO: #TEST-21:30min Puzzle description ``` Here `DES` and `DEV` are the roles of people who must fix that puzzles; `45min` and `40m` is the amount of time the puzzle should take; -`224`, `TEST-13`, `55`, `67`, `678`, `1`, and `42` are the IDs of the tickets +`224`, `55`, `67`, `678` and `TEST-21` are the IDs of the tickets these puzzles are coming from. Markers are absolutely necessary for all puzzles, because they allow @@ -162,6 +164,43 @@ us to build a hierarchical dependency tree of all puzzles, like for example. Technically, of course, you can abuse the system and put a dummy `#1` marker everywhere. +### Multiline examples + +For multiline puzzles there are two important things: +- **prefix** - any optional text followed by space before puzzle keyword (todo). +It should be the same for all lines of puzzle description. +- \ symbol can be used to logically divide puzzle description. +prefix should be presented with it. + +Examples: + +```xml + +``` + +```java +/** + * @todo #36 Multiline text can use the same prefix in all lines or the same + * amount of spaces. + * So this will be added to the puzzle description. If you want to divide the + * puzzle logically by empty line, just add a backspace to that line + * \ + * and continue the text after. + * + * This line is not part of the puzzle, because the line before does not contain + * prefix. + */ +``` + ## How to Configure Rules? You can specify post-parsing rules for your puzzles, in command line, diff --git a/Rakefile b/Rakefile index bc476df..9508150 100644 --- a/Rakefile +++ b/Rakefile @@ -74,7 +74,7 @@ end require 'cucumber/rake/task' Cucumber::Rake::Task.new(:features) do |t| - t.cucumber_opts = 'features' + t.cucumber_opts = %w[features --strict-undefined] Rake::Cleaner.cleanup_files(['coverage']) end Cucumber::Rake::Task.new(:'features:html') do |t| diff --git a/lib/pdd/source.rb b/lib/pdd/source.rb index 69037a3..f6c0853 100644 --- a/lib/pdd/source.rb +++ b/lib/pdd/source.rb @@ -144,13 +144,37 @@ def minutes(num, units) def tail(lines, prefix, start) return [] if lines.empty? prefix = " #{' ' * start}" if prefix.empty? # fallback to space indentation - empty_prefix = ' ' * (prefix.length + 1) - prefix = "#{prefix} " if lines[0][prefix.length, 1]&.start_with?(' ') - lines - .take_while do |t| - match_markers(t).none? && (t.start_with?(prefix) || t.start_with?(empty_prefix)) + tail_prefix = puzzle_tail_prefix(lines, prefix) + tail = lines + .take_while { |t| puzzle_text?(t, tail_prefix, prefix) } + .map do |t| + content = t[tail_prefix.length, t.length]&.lstrip + puzzle_empty_line?(content, '') ? '' : content + end + tail.pop if tail[-1].eql?('') + tail + end + + def puzzle_tail_prefix(lines, prefix) + return prefix if lines.empty? + i = 0 + while i < lines.length + unless puzzle_empty_line?(lines[i], prefix) + return lines[i].start_with?("#{prefix} ") ? "#{prefix} " : prefix end - .map { |t| t[prefix.length, t.length]&.lstrip } + i += 1 + end + prefix + end + + def puzzle_text?(line, prefix, intro_prefix) + return false unless match_markers(line).none? + line.start_with?(prefix) || puzzle_empty_line?(line, intro_prefix) + end + + def puzzle_empty_line?(line, prefix) + return true if line.nil? + line.start_with?(prefix) && line.gsub(prefix, '').chomp.strip.eql?('\\') end # @todo #75:30min Let's make it possible to fetch Subversion data diff --git a/test/test_source.rb b/test/test_source.rb index f770f1b..0eb2f07 100644 --- a/test/test_source.rb +++ b/test/test_source.rb @@ -48,7 +48,7 @@ def test_parsing assert_equal 2, list.size puzzle = list.first assert_equal '2-3', puzzle.props[:lines] - assert_equal 'привет, how are you doing?', \ + assert_equal 'привет, how are you doing?', puzzle.props[:body] assert_equal '44', puzzle.props[:ticket] assert puzzle.props[:author].nil? diff --git a/test/test_source_todo.rb b/test/test_source_todo.rb index d6c722d..d13b01f 100644 --- a/test/test_source_todo.rb +++ b/test/test_source_todo.rb @@ -112,6 +112,17 @@ def test_todo_colon_parsing ) end + def test_todo_backslash_escape + check_valid_puzzle( + " + // TODO #45 task description with \\ + ", + '2-2', + 'task description with \\', + '45' + ) + end + def test_multiple_todo_colon check_valid_puzzle( " @@ -150,22 +161,75 @@ def test_todo_colon_parsing_multi_line_with_empty_line ) end - # space is needed in test data in the comment - # rubocop:disable Layout/TrailingWhitespace def test_todo_colon_parsing_multi_line_with_empty_line_and_space check_valid_puzzle( ' // TODO: #46 task description - // - // second line after empty line is a part of the puzzle in case of space exists + // \ + // second line after empty line is a part of the puzzle in case of backslash exists ', '2-4', 'task description second line after empty line is a part ' \ - 'of the puzzle in case of space exists', + 'of the puzzle in case of backslash exists', + '46' + ) + end + + def test_todo_colon_parsing_double_puzzle_with_empty_line + check_valid_puzzle( + ' + // TODO: #46 task description for first + // \ + // TODO: #47 task description + ', + '2-2', + 'task description for first', + '46', + 2 + ) + end + + def test_todo_parsing_puzzle_javadoc_with_empty_line + check_valid_puzzle( + ' + /** + * TODO: #46 task description + * \ + */ + * some text + ', + '3-3', + 'task description', '46' ) end - # rubocop:enable Layout/TrailingWhitespace + + def test_todo_parsing_puzzle_last_empty_line + check_valid_puzzle( + ' + /** + * TODO: #47 task description + * \ +', + '3-3', + 'task description', + '47' + ) + end + + def test_todo_colon_parsing_multi_line_random_prefix + check_valid_puzzle( + ' + ~~ + ~~ @todo #44 First + ~~ and + ~~ second + ', + '3-4', + 'First and', + '44' + ) + end def test_todo_failing_no_ticket check_invalid_puzzle( diff --git a/test_assets/puzzles/44-660e9d6f b/test_assets/puzzles/44-660e9d6f index c65f59c..f662706 100644 --- a/test_assets/puzzles/44-660e9d6f +++ b/test_assets/puzzles/44-660e9d6f @@ -2,7 +2,7 @@ // @todo #44 This puzzle // consists // of -// +// \ // two // paragraphs