Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
quarto-dev
GitHub Repository: quarto-dev/quarto-cli
Path: blob/main/src/resources/rmd/rmd.R
12921 views
1
# rmarkdown.R
2
# Copyright (C) 2020-2022 Posit Software, PBC
3
4
# main
5
.main <- function() {
6
# execute knitr::spin
7
spin <- function(input) {
8
if (utils::packageVersion("knitr") < "1.44") {
9
stop(
10
"knitr >= 1.44 is required for rendering with Quarto from `.R` files. ",
11
"Please update knitr.",
12
call. = FALSE
13
)
14
}
15
16
# read file
17
text <- xfun::read_utf8(input)
18
19
# spin and return
20
knitr::spin(text = text, knit = FALSE, format = "qmd")
21
}
22
23
# dependencies (convert knit_meta to includes)
24
dependencies <- function(
25
input,
26
format,
27
output,
28
tempDir,
29
libDir,
30
data,
31
quiet
32
) {
33
# unserialize knit_meta
34
knit_meta <- lapply(data, jsonlite::unserializeJSON)
35
36
# determine files_dir
37
files_dir <- if (!is.null(libDir)) {
38
libDir
39
} else {
40
rmarkdown:::knitr_files_dir(output)
41
}
42
43
# yield pandoc format
44
list(
45
includes = pandoc_includes(
46
input,
47
format,
48
output,
49
files_dir,
50
knit_meta,
51
tempDir
52
)
53
)
54
}
55
56
# postprocess (restore preserved)
57
postprocess <- function(input, format, output, preserved_chunks) {
58
# check for html output
59
isHTML <- knitr::is_html_output(
60
format$pandoc$to,
61
c("markdown", "epub", "gfm", "commonmark", "commonmark_x", "markua")
62
)
63
64
# bail if we don't have any perserved chunks and aren't doing code linking
65
code_link <- isHTML && isTRUE(format$render$`code-link`)
66
if (length(preserved_chunks) == 0 && code_link == FALSE) {
67
return()
68
}
69
70
# change to input dir and make input relative
71
oldwd <- setwd(dirname(rmarkdown:::abs_path(input)))
72
on.exit(setwd(oldwd), add = TRUE)
73
input <- basename(input)
74
75
# apply r-options (if any)
76
r_options <- format$metadata$`r-options`
77
if (!is.null(r_options)) {
78
do.call(options, r_options)
79
}
80
81
# read output and initialize output_res to no-op
82
output_str <- xfun::read_utf8(output)
83
output_res <- output_str
84
85
# perform code linking if requested
86
if (isTRUE(code_link)) {
87
has_annotations <- function(input) {
88
# Check to see if there are annotations in the file
89
inputLines <- readLines(input)
90
chunkStarts <- grep(knitr::all_patterns$md$chunk.begin, inputLines)
91
chunkEnds <- grep(knitr::all_patterns$md$chunk.end, inputLines)
92
annotations <- grep(".*\\Q#\\E\\s*<[0-9]+>\\s*", inputLines)
93
hasAnnotations <- FALSE
94
if (length(chunkStarts) > 0 && length(annotations) > 0) {
95
lastLine <- max(max(chunkEnds), max(chunkStarts), max(annotations))
96
97
# the chunk is a vector with a position for each line, indicating
98
# whether that line is within a code chunk
99
chunkMap <- rep(FALSE, lastLine)
100
101
# Update the chunk mapt to mark lines that are
102
# within a code chunk
103
for (x in seq_along(chunkStarts)) {
104
start <- chunkStarts[x]
105
# Ensure end is greater than start
106
end <- start
107
for (e in chunkEnds) {
108
if (e > start) {
109
end <- e
110
break
111
}
112
}
113
for (y in start:end) {
114
if (y > start && y < end) {
115
chunkMap[y] <- TRUE
116
}
117
}
118
}
119
120
# look for at least one annotations that is in a code chunk
121
for (a in annotations) {
122
if (chunkMap[a] == TRUE) {
123
hasAnnotations <- TRUE
124
break
125
}
126
}
127
}
128
hasAnnotations
129
}
130
131
if (!has_annotations(input)) {
132
if (
133
requireNamespace("downlit", quietly = TRUE) &&
134
requireNamespace("xml2", quietly = TRUE)
135
) {
136
# run downlit
137
downlit::downlit_html_path(output, output)
138
139
# fix xml2 induced whitespace problems that break revealjs columns
140
# (b/c they depend on inline-block behavior) then reset output_res
141
downlit_output <- paste(xfun::read_utf8(output), collapse = "\n")
142
downlit_output <- gsub(
143
'(</div>)\n(<div class="column")',
144
"\\1\\2",
145
downlit_output
146
)
147
output_res <- strsplit(downlit_output, "\n", fixed = TRUE)[[1]]
148
} else {
149
warning(
150
"The downlit and xml2 packages are required for code linking",
151
call. = FALSE
152
)
153
}
154
} else {
155
warning(
156
"Since code annotations are present, code-linking has been disabled",
157
call. = FALSE
158
)
159
}
160
}
161
162
# restore preserved chunks if requested
163
if (length(preserved_chunks) > 0) {
164
# convert preserved chunks to named character vector
165
names <- names(preserved_chunks)
166
preserved_chunks <- as.character(preserved_chunks)
167
names(preserved_chunks) <- names
168
169
if (isHTML) {
170
# Pandoc adds an empty <p></p> around the IDs of preserved chunks, and we
171
# need to remove these empty tags, otherwise we may have invalid HTML like
172
# <p><div>...</div></p>. For the reason of the second gsub(), see
173
# https://github.com/rstudio/rmarkdown/issues/133.
174
for (i in names(preserved_chunks)) {
175
output_res <- gsub(
176
paste0("<p>", i, "</p>"),
177
i,
178
output_res,
179
fixed = TRUE,
180
useBytes = TRUE
181
)
182
output_res <- gsub(
183
paste0(' id="[^"]*?', i, '[^"]*?" '),
184
' ',
185
output_res,
186
useBytes = TRUE
187
)
188
}
189
output_res <- htmltools::restorePreserveChunks(
190
output_res,
191
preserved_chunks
192
)
193
} else {
194
output_res <- knitr::restore_raw_output(output_str, preserved_chunks)
195
}
196
}
197
198
# re-write output if necessary
199
if (!identical(output_str, output_res)) {
200
xfun::write_utf8(output_res, output)
201
}
202
}
203
204
run <- function(input, port, host) {
205
shiny_args <- list()
206
if (!is.null(port)) {
207
shiny_args$port <- port
208
}
209
if (!is.null(host)) {
210
shiny_args$host <- host
211
}
212
213
# we already ran quarto render before the call to run
214
Sys.setenv(RMARKDOWN_RUN_PRERENDER = "0")
215
216
# run the doc
217
rmarkdown::run(input, shiny_args = shiny_args)
218
}
219
220
# read request from stdin
221
stdin <- file("stdin", "r", encoding = "")
222
input <- readLines(stdin, warn = FALSE, encoding = "UTF-8")
223
close(stdin)
224
225
# parse request and params
226
request <- jsonlite::parse_json(input, simplifyVector = TRUE)
227
params <- request$params
228
229
# Ensuring expected working dir for Quarto
230
# R process workding may be changed by some workflows (in ~/.Rprofile)
231
# https://github.com/quarto-dev/quarto-cli/issues/2646
232
setwd(request$wd) # no need to reset it as R process is closed by R
233
234
# source in helper functions if we have a resourceDir
235
if (!is.null(params$resourceDir)) {
236
res_dir <- file.path(params$resourceDir, "rmd")
237
source(file.path(res_dir, "patch.R"), local = TRUE)
238
source(file.path(res_dir, "execute.R"), local = TRUE)
239
source(file.path(res_dir, "hooks.R"), local = TRUE)
240
}
241
242
# print execute-debug message ("spin" and "run" don't pass format option)
243
debug <- (!request$action %in% c("spin", "run")) &&
244
isTRUE(params$format$execute[["debug"]])
245
if (debug) {
246
message("[knitr engine]: ", request$action)
247
}
248
249
# dispatch request
250
if (request$action == "spin") {
251
result <- spin(params$input)
252
result <- paste(result, collapse = '\n')
253
} else if (request$action == "execute") {
254
result <- execute(
255
params$input,
256
params$format,
257
params$tempDir,
258
params$libDir,
259
params$dependencies,
260
params$cwd,
261
params$params,
262
params$resourceDir,
263
params$handledLanguages,
264
params$markdown
265
)
266
} else if (request$action == "dependencies") {
267
result <- dependencies(
268
params$input,
269
params$format,
270
params$output,
271
params$tempDir,
272
params$libDir,
273
params$dependencies,
274
params$quiet
275
)
276
} else if (request$action == "postprocess") {
277
result <- postprocess(
278
params$input,
279
params$format,
280
params$output,
281
params$preserve
282
)
283
} else if (request$action == "run") {
284
result <- run(params$input, params$port, params$host)
285
}
286
287
# write results
288
if (debug) {
289
message("[knitr engine]: writing results")
290
}
291
resultJson <- jsonlite::toJSON(auto_unbox = TRUE, result)
292
xfun:::write_utf8(paste(resultJson, collapse = "\n"), request[["results"]])
293
if (debug) message("[knitr engine]: exiting")
294
}
295
296
if (!rmarkdown::pandoc_available(error = FALSE)) {
297
# When FALSE, this means no Pandoc is found by rmarkdown, not even on PATH
298
# In that case we configure rmarkdown to use Quarto bundled version
299
quarto_bin_path <- Sys.getenv("QUARTO_BIN_PATH", NA_character_)
300
# Checking env var to be safe, but should always set by Quarto
301
if (!is.na(quarto_bin_path)) {
302
pandoc_dir <- normalizePath(file.path(quarto_bin_path, "tools"))
303
invisible(rmarkdown::find_pandoc(dir = pandoc_dir))
304
}
305
}
306
307
# run main
308
.main()
309
310