diff --git a/NAMESPACE b/NAMESPACE index 5a4802c..f5a08c9 100644 --- a/NAMESPACE +++ b/NAMESPACE @@ -3,6 +3,12 @@ export("%>%") export(bookdown_to_book_txt) export(bookdown_to_leanpub) +export(check_quiz) +export(check_quiz_answers) +export(check_quiz_attributes) +export(check_quizzes) +export(extract_quiz) +export(find_quiz_indices) export(get_image_from_slide) export(get_image_link_from_slide) export(get_slide_page) @@ -11,6 +17,7 @@ export(gs_png_download) export(gs_png_url) export(gs_to_drive_pngs) export(include_slide) +export(parse_quiz) export(remove_yaml_header) export(replace_html) export(replace_single_html) diff --git a/R/bookdown_to_leanpub.R b/R/bookdown_to_leanpub.R index 95ec487..0781494 100644 --- a/R/bookdown_to_leanpub.R +++ b/R/bookdown_to_leanpub.R @@ -82,6 +82,7 @@ copy_bib = function(path = ".", output_dir = "manuscript") { + #' Convert Bookdown to Leanpub #' #' @param path path to the bookdown book, must have a `_bookdown.yml` file @@ -89,6 +90,10 @@ copy_bib = function(path = ".", output_dir = "manuscript") { #' relative to path #' @param render if `TRUE`, then [bookdown::render_book()] will be run on each Rmd. #' @param verbose print diagnostic messages +#' @param remove_resources_start remove the word `resources/` at the front +#' of any image path. +#' @param make_book_txt Should [leanbuild::bookdown_to_book_txt()] be run +#' to create a `Book.txt` in the output directory? #' #' @return A list of output files and diagnostics #' @export @@ -96,6 +101,8 @@ copy_bib = function(path = ".", output_dir = "manuscript") { bookdown_to_leanpub = function(path = ".", render = TRUE, output_dir = "manuscript", + make_book_txt = TRUE, + remove_resources_start = FALSE, verbose = TRUE) { rmd_regex = "[.][R|r]md$" @@ -108,6 +115,9 @@ bookdown_to_leanpub = function(path = ".", }) rmd_files = bookdown_rmd_files(path = path) if (render) { + if (verbose) { + message("Rendering the Book") + } input = rmd_files[grepl("index", rmd_files, ignore.case = TRUE)][1] if (length(input) == 0 || is.na(input)) { input = rmd_files[1] @@ -119,8 +129,17 @@ bookdown_to_leanpub = function(path = ".", } # may be irrelevant since copy_docs does everything + if (verbose) { + message("Copying Resources") + } copy_resources(path, output_dir = output_dir) + if (verbose) { + message("Copying Docs folder") + } copy_docs(path, output_dir = output_dir) + if (verbose) { + message("Copying bib files") + } copy_bib(path, output_dir = output_dir) # FIXME Can also use bookdown_rmd_files # rmd_files = list.files(pattern = rmd_regex) @@ -159,8 +178,12 @@ bookdown_to_leanpub = function(path = ".", # ) # } infile = normalizePath(file) - infile = replace_single_html(infile) + infile = replace_single_html(infile, verbose = verbose > 1, + remove_resources_start = remove_resources_start) if (length(bib_files) > 0) { + if (verbose > 1) { + message("Making references") + } writeLines(simple_references(infile, bib_files, add_reference_header = TRUE), con = infile, sep = "\n") } @@ -173,7 +196,14 @@ bookdown_to_leanpub = function(path = ".", # citeproc = TRUE, # verbose = verbose) } + out = NULL + if (make_book_txt) { + out = bookdown_to_book_txt( path = path, + output_dir = output_dir, + verbose = verbose) + } L = list(output_files = md_files) + L$book_txt_output = out return(L) } @@ -185,14 +215,16 @@ bookdown_to_leanpub = function(path = ".", #' relative to path #' @param verbose print diagnostic messages #' -#' @return A list of output files and diagnostics +#' @return A list of output files in order, the book text file name, and diagnostics #' @export -#' -#' @examples bookdown_to_book_txt = function( path = ".", output_dir = "manuscript", verbose = TRUE) { + + index = full_file = NULL + rm(list = c("full_file", "index")) + path = bookdown_path(path) owd = getwd() setwd(path) @@ -204,7 +236,7 @@ bookdown_to_book_txt = function( md_files = sub(rmd_regex, ".md", rmd_files, ignore.case = TRUE) md_df = tibble::tibble( file = md_files, - order = seq_along(md_files) + index = seq_along(md_files) ) quiz_files = paste0("quiz-", md_files) bad_quiz_files = paste0("quiz_", md_files) @@ -215,33 +247,34 @@ bookdown_to_book_txt = function( # add 0.5 so it's after the correct md file quiz_df = tibble::tibble( file = quiz_files, - order = seq_along(quiz_files) + 0.5 + index = seq_along(quiz_files) + 0.5 ) bad_quiz_df = tibble::tibble( file = bad_quiz_files, - order = seq_along(bad_quiz_files) + 0.5 + index = seq_along(bad_quiz_files) + 0.5 ) quiz_df = dplyr::bind_rows(quiz_df, bad_quiz_df) rm(list = c("bad_quiz_files", "bad_quiz_df")) quiz_df = quiz_df %>% - dplyr::arrange(order) - df = dplyr::bind_rows(md_df, quiz_df) %>% - rm(list = c("bad_quiz_files", "bad_quiz_df")) - - # quiz_df = quiz_df %>% - # dplyr::mutate(file = file.path(output_dir, file)) - + dplyr::arrange(index) + df = dplyr::bind_rows(md_df, quiz_df) + rm(list = c("quiz_df")) - stop("Not done - need to fix quizzes") + df = df %>% + dplyr::arrange(index) + df = df %>% + dplyr::mutate(full_file = file.path(output_dir, file)) + df = df %>% + dplyr::filter(file.exists(full_file)) book_txt = file.path(output_dir, "Book.txt") - if (file.exists(book_txt)) { - x = readLines(book_txt) - quiz = x[grepl("quiz.md", x)] - } # need to fix about quiz - writeLines(md_files, book_txt) - return(book_txt) + writeLines(df$file, book_txt) + L = list( + md_order = df, + book_file = book_txt + ) + return(L) } diff --git a/R/quiz.R b/R/quiz.R index 9076fa7..22692f6 100644 --- a/R/quiz.R +++ b/R/quiz.R @@ -1,4 +1,173 @@ +find_question = function(x) { + grepl("^\\?", x) +} + +extract_number = function(x) { + bad = !find_question(x) + out = gsub("^\\?(\\d*)\\s*.*", "\\1", x) + out[bad] = NA + out +} + +find_answer = function(x) { + grepl("^([[:alpha:]]\\)|!)", x) +} + + +find_metadata = function(x) { + grepl("^\\{", x) +} + +# note this is not for general metadata +# For example, {id: "this is , my id", number: 2} +# will fail in this example +extract_meta = function(x) { + x = trimws(x) + x = sub("^\\{", "", x) + x = sub("\\}$", "", x) + x = strsplit(x, ",") + out = lapply(x, function(xx) { + xx = trimws(xx) + xx = xx[ !xx %in% "" ] + xxx = strsplit(xx, ":") + xxx = sapply(xxx, function(r) { + if (length(r) <= 1) { + return(r) + } + r[2] = paste(r[2:length(r)], collapse = ":") + r = r[1:2] + r = trimws(r) + nr = r[1] + r = r[2] + names(r) = nr + nr = as.list(r) + r + }) + xxx = as.list(xxx) + if (length(xxx) == 0) { + xxx = NULL + } + xxx + # xxx = unlist(c(xxx)) + }) + out +} + + +#' Parse Quiz +#' +#' @param x A single filename or a vector of the contents of the markdown +#' file +#' +#' @return A list of elements, including a `data.frame` and metadata +#' for questions +#' @export +#' +#' @examples +#' +#' x = c('{quiz, id: quiz_00_filename}', +#' "### Lesson Name quiz", +#' "{choose-answers: 4}", +#' "? What do you think?", +#' "C) The answer to this one", +#' "o) Not the answer", +#' "o) Not the answer either", +#' "C) Another correct answer", +#' "m) Mandatory different answer", +#' "{/quiz}") +#' out = parse_quiz(x) +#' check_quiz_attributes(out) +parse_quiz = function(x) { + if (length(x) == 1 && file.exists(x)) { + x = readLines(x, warn = FALSE) + } + answer = meta = repeated = question = number = NULL + rm(list = c("number", "question", "repeated", "answer", "meta")) + + df = extract_quiz(x) + + + if (length(df) == 0) { + return(NULL) + } + stopifnot(length(df) >= 2) + quiz_meta = df[1] + full_quiz_spec = quiz_meta + quiz_meta = sub("\\{\\s*quiz(,|)", "{", quiz_meta) + + # remove the "/quiz" + df = df[2:(length(df)-1)] + df = tibble::tibble( + original = df, + x = trimws(df, which = "left") + ) + # df = df %>% + # dplyr::filter(!x %in% "") + df = df %>% + dplyr::mutate( + question = find_question(x), + answer = find_answer(x), + number = extract_number(x), + meta = find_metadata(x), + number = ifelse(number == "", NA, number), + repeated = duplicated(number) & !is.na(number) + ) + df = df %>% + dplyr::select(-number) + types = df %>% + dplyr::select(answer, meta, question) + types = rowSums(types, na.rm = TRUE) + stopifnot(all(types <= 1)) + df = df %>% + dplyr::mutate( + type = dplyr::case_when( + question ~ "question", + answer ~ "answer", + meta ~ "metadata", + trimws(x) == "" ~ "spacing", + TRUE ~ "markdown" + )) + # trying to caputure ?1 and ?1 for multiple questions + df = df %>% + dplyr::mutate( + question = ifelse(repeated, FALSE, question) + ) + df = df %>% + dplyr::mutate(question = cumsum(question)) + # assign the question number to the next line, as it should be the question + df = df %>% + dplyr::mutate(question = ifelse(meta, dplyr::lead(question), question)) + df = df %>% + dplyr::select(-answer, -meta) + meta = df$x[df$type == "metadata"] + meta = extract_meta(meta) + quiz_meta = extract_meta(quiz_meta)[[1]] + L = list( + data = df, + question_metadata = meta, + original_quiz_specification = full_quiz_spec, + quiz_metadata = quiz_meta + ) + L +} + + + + +#' @export +#' @rdname parse_quiz +extract_quiz = function(x) { + xx = find_quiz_indices(x) + ind = seq(xx[1], xx[2]) + x[ind] +} + +#' @export +#' @rdname parse_quiz find_quiz_indices = function(x) { + if (length(x) == 1 && file.exists(x)) { + x = readLines(x, warn = FALSE) + } start = grep("^\\s*\\{\\s*quiz", x) end = grep("^\\s*\\{\\s*/\\s*quiz", x) stopifnot( @@ -12,3 +181,174 @@ find_quiz_indices = function(x) { out } + +#' Check Quiz Information +#' +#' @param x The output from [leanbuild::parse_quiz] +#' +#' @return A logical +#' @export +#' +#' @examples +#' +#' x = c('{quiz, id: quiz_00_filename}', +#' "### Lesson Name quiz", +#' "{choose-answers: 4}", +#' "? What do you think?", +#' "C) The answer to this one", +#' "o) Not the answer", +#' "o) Not the answer either", +#' "C) Another correct answer", +#' "m) Mandatory different answer", +#' "{/quiz}") +#' out = parse_quiz(x) +#' check_quiz_attributes(out) +#' check_quiz_answers(out) +#' @rdname check_quiz +check_quiz_attributes = function(x) { + if (length(x) == 1 && file.exists(x)) { + x = parse_quiz(x) + } + quiz_metadata = x$quiz_metadata + quiz_metadata = tibble::as_tibble(quiz_metadata) + + + quiz_attributes = c("version", + "attempts", + "case-sensitive", + "id", + "points", + "random-choice-order", + "random-question-order", + "start-at", + "version") + + result = TRUE + if (NROW(quiz_metadata) > 0) { + sd = setdiff(colnames(quiz_metadata), quiz_attributes) + if (length(sd) > 0) { + warning( + paste0("quiz has attributes that seem not for questions:", + paste(sd, collapse = ", ")) + ) + result = FALSE + } + } + return(result) +} + +#' @export +#' @rdname check_quiz +#' @param verbose print diagnostic messages +check_quiz_answers = function(x, verbose = TRUE) { + type = answer = meta = repeated = question = number = NULL + rm(list = c("number", "question", "repeated", "answer", + "meta", "type")) + + out = x$data + quiz_question_attributes = c("choose-answers", + "points", + "random-choice-order") + + if (is.null(out)) { + return(TRUE) + } + result = TRUE + out = out %>% + dplyr::filter(question >= 1) + out = split(out, out$question) + out = lapply(out, function(r) { + question_name = unique(paste0("question_", r$question)) + if (verbose > 1) { + message(question_name) + } + meta = r %>% + dplyr::filter(type == "metadata") + meta = extract_meta(meta$original) + meta = dplyr::bind_rows(lapply(meta, tibble::as_tibble)) + if (NROW(meta) > 0) { + sd = setdiff(colnames(meta), quiz_question_attributes) + if (length(sd) > 0) { + warning( + paste0(question_name, " has attributes that seem not for questions:", + paste(sd, collapse = ", ")) + ) + result <<- FALSE + } + } + r = r %>% + dplyr::filter(type == "answer") + if (NROW(r) == 0) { + result <<- FALSE + warning(paste0(question_name, " has no listed answers")) + } + return(NULL) + }) + return(result) +} + + + +quiz_md_files = function(path = "manuscript") { + files = list.files(pattern = "quiz.*[.]md", ignore.case = TRUE, + path = path, full.names = FALSE) + return(files) +} + + + +#' Check Quizzes +#' +#' @param path either a path to the directory of quizzes or a full path to a +#' quiz markdown file +#' +#' @return A list of logical indicators +#' @export +#' +#' @examples +#' +#' x = c('{quiz, id: quiz_00_filename}', +#' "### Lesson Name quiz", +#' "{choose-answers: 4}", +#' "? What do you think?", +#' "C) The answer to this one", +#' "o) Not the answer", +#' "o) Not the answer either", +#' "C) Another correct answer", +#' "m) Mandatory different answer", +#' "{/quiz}") +#' tdir = tempfile() +#' dir.create(tdir, showWarnings = FALSE, recursive = TRUE) +#' tfile = tempfile(pattern = "quiz_", fileext = ".md", tmpdir = tdir) +#' writeLines(x, tfile) +#' check_quizzes(path = tdir) +#' +#' check_quiz(x) +check_quizzes = function(path = "manuscript") { + owd = getwd() + setwd(path) + on.exit({ + setwd(owd) + }) + files = quiz_md_files(path = path) + if (length(files) == 0) return(TRUE) + result = lapply(files, check_quiz) + names(result) = files + result = sapply(result, function(x) { + all(x$quiz_answer_output & x$quiz_spec_output) + }) + return(result) +} + +#' @export +#' @rdname check_quizzes +check_quiz = function(path) { + out = parse_quiz(path) + quiz_spec_output = check_quiz_attributes(out) + quiz_answer_output = check_quiz_answers(out) + return(list( + quiz_df = out, + quiz_answer_output = quiz_answer_output, + quiz_spec_output = quiz_spec_output + )) +} diff --git a/R/replace_html.R b/R/replace_html.R index f669b55..bc4f2af 100644 --- a/R/replace_html.R +++ b/R/replace_html.R @@ -199,7 +199,12 @@ margin_to_align = function(x) { build_image = function(src, ..., caption = NULL, embed = NULL, - fullbleed = FALSE) { + fullbleed = FALSE, + remove_resources_start = TRUE) { + if (remove_resources_start) { + src = gsub("^resources/", "", src) + } + myenv = list(..., caption = caption, embed = embed, @@ -226,7 +231,7 @@ build_image = function(src, ..., caption = NULL, embed = NULL, x } -replace_div_data = function(x, fullbleed = FALSE) { +replace_div_data = function(x, fullbleed = FALSE, remove_resources_start = TRUE) { div_index = find_figure_div(x) if (NROW(div_index) == 0) { return(x) @@ -252,6 +257,7 @@ replace_div_data = function(x, fullbleed = FALSE) { } } args = lapply(args, empty_to_null) + args$remove_resources_start = remove_resources_start do.call(build_image, args = args) }) first_div_index = sapply(div_indices, dplyr::first) @@ -265,7 +271,8 @@ replace_div_data = function(x, fullbleed = FALSE) { x } -replace_image_data = function(x, element = c("img", "iframe"), fullbleed = FALSE) { +replace_image_data = function(x, element = c("img", "iframe"), fullbleed = FALSE, + remove_resources_start = TRUE) { element = match.arg(element) func = switch(element, img = find_img, @@ -288,7 +295,7 @@ replace_image_data = function(x, element = c("img", "iframe"), fullbleed = FALSE # style="display: block; margin: auto;" is center image_attributes = lapply(images, function(x) { out = lapply(attributes, function(name) { - na_empty(get_img_attr(x = x, name = name)) + na_empty(get_html_attr(x = x, name = name, element = element)) }) names(out) = attributes out$margin = get_margin(out$style) @@ -302,6 +309,7 @@ replace_image_data = function(x, element = c("img", "iframe"), fullbleed = FALSE out_images = sapply(image_attributes, function(args) { + args$remove_resources_start = remove_resources_start do.call(build_image, args = args) }) out_images = c(unlist(out_images)) @@ -321,28 +329,48 @@ replace_image_data = function(x, element = c("img", "iframe"), fullbleed = FALSE #' #' @param path path to the markdown files that need replacement. #' @param fullbleed should the image have the attribute `fullbleed: true`? +#' @param remove_resources_start remove the word `resources/` at the front +#' of any image path. #' @param verbose print diagnostic messages #' #' @return A list of output files and diagnostics #' @export replace_html = function(path = "manuscript", + remove_resources_start = TRUE, fullbleed = FALSE, verbose = TRUE) { md_files = list.files(path = path, pattern = "[.]md$", ignore.case = TRUE, full.names = TRUE) - md_files = lapply(md_files, replace_single_html, fullbleed = fullbleed) + md_files = lapply(md_files, replace_single_html, fullbleed = fullbleed, + verbose = verbose) return(md_files) } #' @param file individual markdown file #' @export #' @rdname replace_html -replace_single_html = function(file, fullbleed = FALSE) { +replace_single_html = function(file, + remove_resources_start = TRUE, + fullbleed = FALSE, verbose = TRUE) { stopifnot(length(file) == 1 && file.exists(file)) x = readLines(file, warn = FALSE) - x = replace_div_data(x, fullbleed = fullbleed) - x = replace_image_data(x, element = "img", fullbleed = fullbleed) - x = replace_image_data(x, element = "iframe", fullbleed = fullbleed) + if (verbose) { + message("Replacing Div data") + } + x = replace_div_data(x, fullbleed = fullbleed, + remove_resources_start = remove_resources_start) + + if (verbose) { + message("Replacing image data") + } + x = replace_image_data(x, element = "img", fullbleed = fullbleed, + remove_resources_start = remove_resources_start) + + if (verbose) { + message("Replacing iframe data") + } + x = replace_image_data(x, element = "iframe", fullbleed = fullbleed, + remove_resources_start = remove_resources_start) # need to actually do changes writeLines(x, con = file) diff --git a/man/bookdown_to_book_txt.Rd b/man/bookdown_to_book_txt.Rd index a00412f..355945c 100644 --- a/man/bookdown_to_book_txt.Rd +++ b/man/bookdown_to_book_txt.Rd @@ -15,7 +15,7 @@ relative to path} \item{verbose}{print diagnostic messages} } \value{ -A list of output files and diagnostics +A list of output files in order, the book text file name, and diagnostics } \description{ Convert Bookdown to Leanpub diff --git a/man/bookdown_to_leanpub.Rd b/man/bookdown_to_leanpub.Rd index f5217de..ce49853 100644 --- a/man/bookdown_to_leanpub.Rd +++ b/man/bookdown_to_leanpub.Rd @@ -8,6 +8,8 @@ bookdown_to_leanpub( path = ".", render = TRUE, output_dir = "manuscript", + make_book_txt = TRUE, + remove_resources_start = FALSE, verbose = TRUE ) } @@ -19,6 +21,12 @@ bookdown_to_leanpub( \item{output_dir}{output directory to put files. It should likely be relative to path} +\item{make_book_txt}{Should [leanbuild::bookdown_to_book_txt()] be run +to create a `Book.txt` in the output directory?} + +\item{remove_resources_start}{remove the word `resources/` at the front +of any image path.} + \item{verbose}{print diagnostic messages} } \value{ diff --git a/man/check_quiz.Rd b/man/check_quiz.Rd new file mode 100644 index 0000000..362142a --- /dev/null +++ b/man/check_quiz.Rd @@ -0,0 +1,38 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/quiz.R +\name{check_quiz_attributes} +\alias{check_quiz_attributes} +\alias{check_quiz_answers} +\title{Check Quiz Information} +\usage{ +check_quiz_attributes(x) + +check_quiz_answers(x, verbose = TRUE) +} +\arguments{ +\item{x}{The output from [leanbuild::parse_quiz]} + +\item{verbose}{print diagnostic messages} +} +\value{ +A logical +} +\description{ +Check Quiz Information +} +\examples{ + +x = c('{quiz, id: quiz_00_filename}', +"### Lesson Name quiz", +"{choose-answers: 4}", +"? What do you think?", +"C) The answer to this one", +"o) Not the answer", +"o) Not the answer either", +"C) Another correct answer", +"m) Mandatory different answer", +"{/quiz}") +out = parse_quiz(x) +check_quiz_attributes(out) +check_quiz_answers(out) +} diff --git a/man/check_quizzes.Rd b/man/check_quizzes.Rd new file mode 100644 index 0000000..0a8fc4c --- /dev/null +++ b/man/check_quizzes.Rd @@ -0,0 +1,41 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/quiz.R +\name{check_quizzes} +\alias{check_quizzes} +\alias{check_quiz} +\title{Check Quizzes} +\usage{ +check_quizzes(path = "manuscript") + +check_quiz(path) +} +\arguments{ +\item{path}{either a path to the directory of quizzes or a full path to a +quiz markdown file} +} +\value{ +A list of logical indicators +} +\description{ +Check Quizzes +} +\examples{ + +x = c('{quiz, id: quiz_00_filename}', +"### Lesson Name quiz", +"{choose-answers: 4}", +"? What do you think?", +"C) The answer to this one", +"o) Not the answer", +"o) Not the answer either", +"C) Another correct answer", +"m) Mandatory different answer", +"{/quiz}") +tdir = tempfile() +dir.create(tdir, showWarnings = FALSE, recursive = TRUE) +tfile = tempfile(pattern = "quiz_", fileext = ".md", tmpdir = tdir) +writeLines(x, tfile) +check_quizzes(path = tdir) + +check_quiz(x) +} diff --git a/man/parse_quiz.Rd b/man/parse_quiz.Rd new file mode 100644 index 0000000..c0285fb --- /dev/null +++ b/man/parse_quiz.Rd @@ -0,0 +1,40 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/quiz.R +\name{parse_quiz} +\alias{parse_quiz} +\alias{extract_quiz} +\alias{find_quiz_indices} +\title{Parse Quiz} +\usage{ +parse_quiz(x) + +extract_quiz(x) + +find_quiz_indices(x) +} +\arguments{ +\item{x}{A single filename or a vector of the contents of the markdown +file} +} +\value{ +A list of elements, including a `data.frame` and metadata +for questions +} +\description{ +Parse Quiz +} +\examples{ + +x = c('{quiz, id: quiz_00_filename}', +"### Lesson Name quiz", +"{choose-answers: 4}", +"? What do you think?", +"C) The answer to this one", +"o) Not the answer", +"o) Not the answer either", +"C) Another correct answer", +"m) Mandatory different answer", +"{/quiz}") +out = parse_quiz(x) +check_quiz_attributes(out) +} diff --git a/man/replace_html.Rd b/man/replace_html.Rd index 2dc1691..1fa0d10 100644 --- a/man/replace_html.Rd +++ b/man/replace_html.Rd @@ -5,13 +5,26 @@ \alias{replace_single_html} \title{Replace HTML and other Tags in Leanpub Markdown} \usage{ -replace_html(path = "manuscript", fullbleed = FALSE, verbose = TRUE) +replace_html( + path = "manuscript", + remove_resources_start = TRUE, + fullbleed = FALSE, + verbose = TRUE +) -replace_single_html(file, fullbleed = FALSE) +replace_single_html( + file, + remove_resources_start = TRUE, + fullbleed = FALSE, + verbose = TRUE +) } \arguments{ \item{path}{path to the markdown files that need replacement.} +\item{remove_resources_start}{remove the word `resources/` at the front +of any image path.} + \item{fullbleed}{should the image have the attribute `fullbleed: true`?} \item{verbose}{print diagnostic messages}