From 101e5f6b1584ff917491974298aa64c72af8df79 Mon Sep 17 00:00:00 2001 From: Russell Hyde Date: Mon, 17 Sep 2018 19:44:27 +0100 Subject: [PATCH 1/3] fixes #252: Allow plain-code-blocks in RMarkdown Added a test to the end of test.Rmd that included a plain-code-block like this: ``` This shouldn't cause a problem ``` The code-block reproduced the `Malformed File` error described in #252 Plain-blocks match the knitr `chunk.end` pattern at both the start and the end of the block. So when plain-blocks are present there was 2k _more_ chunk ends than starts. A small function `filter_chunk_end_positions` was added to filter the positions of the chunk-ends based on the chunk-starts --- R/extract.R | 31 ++++++++++++++++++++++++++- tests/testthat/knitr_formats/test.Rmd | 5 +++++ 2 files changed, 35 insertions(+), 1 deletion(-) diff --git a/R/extract.R b/R/extract.R index d6d4af60f..d1f3e44c6 100644 --- a/R/extract.R +++ b/R/extract.R @@ -1,3 +1,29 @@ +filter_chunk_end_positions <- function(starts, ends) { + # In a valid file, possibly with plain-code-blocks, + # - there should be at least as many ends as starts, + # and there should be an even-number of extra ends (possibly zero) + # since each plain-code-block should open & close, and the open/close + # tags of a plain-code-block both match the chunk.end pattern + + # starts (1, 3, 5, 7, 11) --> (1, 3, 5, 7, 11) + # ends (2, 4, 6, 8, 9, 10, 12) --> (2, 4, 6, 8, 12) # return this + length_difference <- length(ends) - length(starts) + + if(length_difference < 0 | length_difference %% 2 != 0) { + stop("Malformed file!", call. = FALSE) + } + if (length_difference == 0 && all(ends > starts)) { + return(ends) + } + + positions <- sort(c(starts = starts, ends = ends)) + code_start_indexes <- which(grepl("starts", names(positions))) + code_ends <- positions[1 + code_start_indexes] + + stopifnot(all(grepl("ends", names(code_ends)))) + code_ends +} + # content is the file content from readLines extract_r_source <- function(filename, lines) { @@ -7,7 +33,10 @@ extract_r_source <- function(filename, lines) { } starts <- grep(pattern$chunk.begin, lines, perl = TRUE) - ends <- grep(pattern$chunk.end, lines, perl = TRUE) + ends <- filter_chunk_end_positions( + starts = starts, + ends = grep(pattern$chunk.end, lines, perl = TRUE) + ) # no chunks found, so just return the lines if (length(starts) == 0 || length(ends) == 0) { diff --git a/tests/testthat/knitr_formats/test.Rmd b/tests/testthat/knitr_formats/test.Rmd index 18baf1b5e..e98e292bf 100644 --- a/tests/testthat/knitr_formats/test.Rmd +++ b/tests/testthat/knitr_formats/test.Rmd @@ -29,3 +29,8 @@ a=[] a[0]=1 ``` + +``` +Plain code blocks can be written after three or more backticks +- R Markdown: The Definitive Guide. Xie, Allaire and Grolemund (2.5.2) +``` From 49d9826e27e3c34853d77479b2cd8f63bba65241 Mon Sep 17 00:00:00 2001 From: Russell Hyde Date: Tue, 18 Sep 2018 10:29:10 +0100 Subject: [PATCH 2/3] updates NEWS: fixed plain-code-block bug --- NEWS.md | 1 + 1 file changed, 1 insertion(+) diff --git a/NEWS.md b/NEWS.md index ce1b4bd5d..60d11cb24 100644 --- a/NEWS.md +++ b/NEWS.md @@ -56,6 +56,7 @@ avoid reading and pre-processing of ignored files (@mwaldstein) * Allow for any number of `#` to start a comment. Useful in ESS (#299, @prosoitos) * New equals_na_linter() (#143, #326, @jabranham) +* Fixed plain-code-block bug in Rmarkdown (#252, @russHyde) # lintr 1.0.2 # * Fix tests to work with upcoming testthat release. From 52a9f4747ab8e47902d6f3fc86959d98cf89852a Mon Sep 17 00:00:00 2001 From: Russell Hyde Date: Tue, 18 Sep 2018 13:42:22 +0100 Subject: [PATCH 3/3] reordered the functions Main function `extract_r_source` now precedes all it's helper functions (get_knitr_pattern, filter_chunk_end_positions, replace_prefix) --- R/extract.R | 52 ++++++++++++++++++++++++++-------------------------- 1 file changed, 26 insertions(+), 26 deletions(-) diff --git a/R/extract.R b/R/extract.R index d1f3e44c6..36e6022ab 100644 --- a/R/extract.R +++ b/R/extract.R @@ -1,29 +1,3 @@ -filter_chunk_end_positions <- function(starts, ends) { - # In a valid file, possibly with plain-code-blocks, - # - there should be at least as many ends as starts, - # and there should be an even-number of extra ends (possibly zero) - # since each plain-code-block should open & close, and the open/close - # tags of a plain-code-block both match the chunk.end pattern - - # starts (1, 3, 5, 7, 11) --> (1, 3, 5, 7, 11) - # ends (2, 4, 6, 8, 9, 10, 12) --> (2, 4, 6, 8, 12) # return this - length_difference <- length(ends) - length(starts) - - if(length_difference < 0 | length_difference %% 2 != 0) { - stop("Malformed file!", call. = FALSE) - } - if (length_difference == 0 && all(ends > starts)) { - return(ends) - } - - positions <- sort(c(starts = starts, ends = ends)) - code_start_indexes <- which(grepl("starts", names(positions))) - code_ends <- positions[1 + code_start_indexes] - - stopifnot(all(grepl("ends", names(code_ends)))) - code_ends -} - # content is the file content from readLines extract_r_source <- function(filename, lines) { @@ -75,6 +49,32 @@ get_knitr_pattern <- function(filename, lines) { } } +filter_chunk_end_positions <- function(starts, ends) { + # In a valid file, possibly with plain-code-blocks, + # - there should be at least as many ends as starts, + # and there should be an even-number of extra ends (possibly zero) + # since each plain-code-block should open & close, and the open/close + # tags of a plain-code-block both match the chunk.end pattern + + # starts (1, 3, 5, 7, 11) --> (1, 3, 5, 7, 11) + # ends (2, 4, 6, 8, 9, 10, 12) --> (2, 4, 6, 8, 12) # return this + length_difference <- length(ends) - length(starts) + + if(length_difference < 0 | length_difference %% 2 != 0) { + stop("Malformed file!", call. = FALSE) + } + if (length_difference == 0 && all(ends > starts)) { + return(ends) + } + + positions <- sort(c(starts = starts, ends = ends)) + code_start_indexes <- which(grepl("starts", names(positions))) + code_ends <- positions[1 + code_start_indexes] + + stopifnot(all(grepl("ends", names(code_ends)))) + code_ends +} + replace_prefix <- function(lines, prefix_pattern) { if (is.null(prefix_pattern)) { return(lines)