forked from yihui/knitr
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathutils-conversion.R
295 lines (282 loc) · 13.4 KB
/
utils-conversion.R
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
#' A wrapper for rst2pdf
#'
#' Convert reST to PDF using \command{rst2pdf} (which converts from rst to PDF
#' using the ReportLab open-source library).
#' @param input The input rst file.
#' @param command Character string giving the path of the
#' \command{rst2pdf} program. If the program is not in your PATH, the full path has to be
#' given here.
#' @param options Extra command line options, e.g. \code{'-v'}.
#' @author Alex Zvoleff and Yihui Xie
#' @return An input file \file{*.rst} will produce \file{*.pdf} and this output
#' filename is returned if the conversion was successful.
#' @export
#' @seealso \code{\link{knit2pdf}}
#' @references \url{https://github.com/rst2pdf/rst2pdf}
rst2pdf = function(input, command = 'rst2pdf', options = '') {
out = with_ext(input, 'pdf')
system2(command, paste(shQuote(input), '-o', shQuote(out), options))
if (file.exists(out)) out else stop('conversion by rst2pdf failed!')
}
#' Convert various input files to various output files using \code{knit()} and
#' Pandoc
#'
#' Knits the input file and compiles to an output format using Pandoc.
#' @inheritParams knit
#' @param to Character string giving the Pandoc output format to use.
#' @param pandoc_wrapper An R function used to call Pandoc. If \code{NULL} (the
#' default), \code{rmarkdown::\link[rmarkdown]{pandoc_convert}()} will be used
#' if \pkg{rmarkdown} is installed, otherwise \code{\link{pandoc}()}.
#' @param ... Options to be passed to the \code{pandoc_wrapper} function.
#' @param encoding Ignored (always assumes UTF-8).
#' @author Trevor L. Davis
#' @return Returns the output of the \code{pandoc_wrapper} function.
#' @export
knit2pandoc = function(
input, output = NULL, tangle = FALSE, text = NULL, quiet = FALSE,
envir = parent.frame(), to = 'html', pandoc_wrapper = NULL, ..., encoding = 'UTF-8'
) {
knit_output = knit(input, output, tangle, text, quiet, envir)
if (!is.null(pandoc_wrapper)) return(pandoc_wrapper(knit_output, to, ...))
if (!has_package('rmarkdown')) return(pandoc(knit_output, to, ...))
output = gsub(paste0(file_ext(knit_output), '$'), to, knit_output)
rmarkdown::pandoc_convert(knit_output, to, output = output, ...)
}
#' Convert Rnw or Rrst files to PDF
#'
#' Knit the input Rnw or Rrst document, and compile to PDF using
#' \code{tinytex::\link[tinytex]{latexmk}()} or \code{\link{rst2pdf}()}.
#' @inheritParams knit
#' @param compiler A character string giving the LaTeX engine used to compile
#' the tex document to PDF. For an Rrst file, setting \code{compiler} to
#' \code{'rst2pdf'} will use \code{\link{rst2pdf}} to compile the rst file to
#' PDF using the ReportLab open-source library.
#' @param ... Options to be passed to \code{tinytex::\link[tinytex]{latexmk}()}
#' or \code{\link{rst2pdf}()}.
#' @author Ramnath Vaidyanathan, Alex Zvoleff and Yihui Xie
#' @return The filename of the PDF file.
#' @note The \code{output} argument specifies the output filename to be passed
#' to the PDF compiler (e.g. a tex document) instead of the PDF filename.
#' @export
#' @examples #' compile with xelatex
#' ## knit2pdf(..., compiler = 'xelatex')
#'
#' #' compile a reST file with rst2pdf
#' ## knit2pdf(..., compiler = 'rst2pdf')
knit2pdf = function(
input, output = NULL, compiler = NULL, envir = parent.frame(), quiet = FALSE, ...
) {
out = knit(input, output = output, envir = envir, quiet = quiet)
owd = setwd(dirname(out)); on.exit(setwd(owd))
if (is.null(compiler)) {
compiler = if (grepl('\\.rst$', out)) 'rst2pdf' else 'pdflatex'
}
if (identical(compiler, 'rst2pdf')) {
if (tolower(file_ext(out)) != 'rst')
stop('for rst2pdf compiler input must be a .rst file')
rst2pdf(basename(out), ...)
} else {
tinytex::latexmk(basename(out), engine = compiler, ...)
}
with_ext(out, 'pdf')
}
#' Convert an \file{Rnw} document to PDF
#'
#' Call \code{\link{knit}()} to compile the \file{.Rnw} input to \file{.tex},
#' and then \code{tinytex::\link[tinytex]{latexmk}()} to convert \file{.tex} to
#' \file{.pdf}.
#'
#' This function is similar to \code{\link{knit2pdf}()}, with the following differences:
#' \enumerate{
#' \item The default compiler is "xelatex" instead of "pdflatex".
#' \item \code{output} uses the file extension ".pdf" instead of ".tex".
#' \item Before knitting, it tries to remove the \code{output} file and will throw a clear error if the file cannot be removed.
#' \item \code{output} could be under any dir, not necessarily the same directory as \code{input}.
#' \item It cleans up intermediate files by default, including the ".tex" file.
#' \item It stops knitting when any error occurs (by setting the chunk option \code{error = FALSE}).
#' }
#' @inheritParams knit
#' @param output Path of the PDF output file. By default, it uses the same name
#' as the \code{input}, but changes the file extension to ".pdf".
#' @param compiler,... The LaTeX engine and other arguments to be passed to
#' \code{tinytex::\link[tinytex]{latexmk}()}. The default compiler is
#' \code{xelatex}.
#' @param clean If \code{TRUE}, the intermediate files will be removed.
#' @param error If \code{FALSE}, knitting stops when any error occurs.
#' @return The \code{output} file path.
#' @export
rnw2pdf = function(
input, output = with_ext(input, 'pdf'), compiler = 'xelatex',
envir = parent.frame(), quiet = FALSE, clean = TRUE, error = FALSE, ...
) {
# On Windows, when tweaking the content, users may forget to close the PDF
# file (thus can't be written). Since knitting may take quite some time, it's
# better to check the write permission of the output file in advance.
if (xfun::file_exists(output) && !file.remove(output)) stop(
"The file '", output, "' cannot be removed (may be locked by a PDF reader)."
)
old = opts_chunk$set(error = error)
on.exit(opts_chunk$set(old), add = TRUE)
file_tex = knit(input, envir = envir, quiet = quiet)
if (clean) on.exit(file.remove(file_tex), add = TRUE)
file_pdf = tinytex::latexmk(file_tex, engine = compiler, clean = clean, ...)
if (!xfun::same_path(output, file_pdf)) file.rename(file_pdf, output)
output
}
#' Convert markdown to HTML using knit() and mark_html()
#'
#' This is a convenience function to knit the input markdown source and call
#' \code{markdown::\link[markdown]{mark_html}()} in the \pkg{markdown}
#' package to convert the result to HTML.
#' @inheritParams knit
#' @param ... Options passed to
#' \code{markdown::\link[markdown]{mark_html}()}.
#' @param force_v1 Boolean; whether to force rendering the input document as an
#' R Markdown v1 document, even if it is for v2.
#' @export
#' @seealso \code{\link{knit}}, \code{markdown::\link[markdown]{mark_html}}
#' @return If the argument \code{text} is NULL, a character string (HTML code)
#' is returned; otherwise the result is written into a file and the filename
#' is returned.
#' @note The \pkg{markdown} package is for R Markdown v1, which is much less
#' powerful than R Markdown v2, i.e. the \pkg{rmarkdown} package
#' (\url{https://rmarkdown.rstudio.com}). To render R Markdown v2 documents to
#' HTML, please use \code{rmarkdown::render()} instead.
#' @examples # a minimal example
#' writeLines(c("# hello markdown", '```{r hello-random, echo=TRUE}', 'rnorm(5)', '```'), 'test.Rmd')
#' knit2html('test.Rmd')
#' if (interactive()) browseURL('test.html')
#'
#' unlink(c('test.Rmd', 'test.html', 'test.md'))
knit2html = function(
input, output = NULL, ..., envir = parent.frame(), text = NULL,
quiet = FALSE, encoding = 'UTF-8', force_v1 = getOption('knitr.knit2html.force_v1', FALSE)
) {
if (is_cran_check() && !has_package('markdown'))
return(vweave_empty(input, .reason = 'markdown'))
if (!force_v1 && is.null(text) && is_rmd_v2(input)) {
warning2(
'It seems you should call rmarkdown::render() instead of knitr::knit2html() ',
'because ', input, ' appears to be an R Markdown v2 document.'
)
}
out = knit(input, text = text, envir = envir, quiet = quiet)
if (is.null(text)) {
output = with_ext(if (is.null(output) || is.na(output)) out else output, 'html')
markdown::mark_html(out, output, ...)
invisible(output)
} else markdown::mark_html(text = out, ...)
}
# test if an Rmd input should be rendered via rmarkdown::render() or (mark|lite)down::mark()
is_rmd_v2 = function(input, text = read_utf8(input)) {
res = xfun::yaml_body(text)$yaml[['output']]
if (is.list(res)) res = names(res)
length(res) > 0 && is.character(res) && !any(grepl('^(mark|lite)down::', res))
}
#' Knit an R Markdown document and post it to WordPress
#'
#' This function is a wrapper around the \pkg{RWordPress} package. It compiles
#' an R Markdown document to HTML and post the results to WordPress. Please note
#' that \pkg{RWordPress} has not been updated for several years, which is
#' \href{https://github.com/yihui/knitr/issues/1866}{not a good sign}. For
#' blogging with R, you may want to try the \pkg{blogdown} package instead.
#' @param input Filename of the Rmd document.
#' @param title Title of the post.
#' @param ... Other meta information of the post, e.g. \code{categories = c('R',
#' 'Stats')} and \code{mt_keywords = c('knitr', 'wordpress')}, et cetera.
#' @param shortcode A length-2 logical vector: whether to use the shortcode
#' \samp{[sourcecode lang='lang']}, which can be useful to WordPress.com users
#' for syntax highlighting of source code and output. The first element
#' applies to source code, and the second applies to text output. By default,
#' both are \code{FALSE}.
#' @param action Whether to create a new post, update an existing post, or
#' create a new page.
#' @param postid If \code{action} is \code{editPost}, the post id \code{postid}
#' must be specified.
#' @param publish Boolean: publish the post immediately?
#' @inheritParams knit
#' @export
#' @references \url{https://yihui.org/knitr/demo/wordpress/}
#' @author William K. Morris, Yihui Xie, and Jared Lander
#' @note This function will convert the encoding of the post and the title to
#' UTF-8 internally. If you have additional data to send to WordPress (e.g.
#' keywords and categories), you may have to manually convert them to the
#' UTF-8 encoding with the \code{\link{iconv}(x, to = 'UTF-8')} function
#' (especially when using Windows).
#' @examples # see the reference
knit2wp = function(
input, title = 'A post from knitr', ..., envir = parent.frame(), shortcode = FALSE,
action = c('newPost', 'editPost', 'newPage'), postid, publish = TRUE
) {
do.call('library', list(package = 'RWordPress', character.only = TRUE))
xfun::do_once(
warning2(
'This function is based on the RWordPress package, which is no longer actively ',
'maintained (https://github.com/yihui/knitr/issues/1866). For blogging with R, ',
'you may try the blogdown package instead.'
), 'knitr.knit2wp.warning'
)
out = knit(input, envir = envir); on.exit(unlink(out))
content = read_utf8(out)
if (missing(title) && length(title2 <- xfun::yaml_body(content)$yaml$title) == 1)
title = title2
content = markdown::mark(text = content)
shortcode = rep(shortcode, length.out = 2L)
if (shortcode[1]) content = gsub(
'<pre><code class="([[:alpha:]]+)">(.+?)</code></pre>',
'[sourcecode language="\\1"]\\2[/sourcecode]', content
)
content = gsub(
'<pre><code( class="no-highlight"|)>(.+?)</code></pre>',
if (shortcode[2]) '[sourcecode]\\2[/sourcecode]' else '<pre>\\2</pre>', content
)
content = enc2utf8(content)
title = enc2utf8(title)
# figure out if we are making a newPost or overwriting an existing post
action = match.arg(action)
# build a list of arguments to be fed into either newPost or editPost
# the first argument is the content, which itself is a list containing
# description
# title
# ...
# then there is the publish argument
WPargs = list(content = list(description = content, title = title, ...), publish = publish)
# if we are editing the post, also include the argument for postid
if (action == "editPost") WPargs = c(postid = postid, WPargs)
do.call(action, args = WPargs)
}
#' Watch an input file continuously and knit it when it is updated
#'
#' Check the modification time of an input file continously in an infinite loop.
#' Whenever the time indicates the file has been modified, call a function to
#' recompile the input file.
#'
#' This is actually a general function not necessarily restricted to
#' applications in \pkg{knitr}. You may specify any \code{compile} function to
#' process the \code{input} file. To stop the infinite loop, press the
#' \samp{Escape} key or \samp{Ctrl + C} (depending on your editing environment
#' and operating system).
#' @param input An input file path, or a character vector of mutiple input file paths.
#' @param compile A function to compile the \code{input} file. This could be e.g.
#' \code{\link{knit}} or \code{\link{knit2pdf}}, depending on the input file
#' and the output you want.
#' @param interval A time interval to pause in each cycle of the infinite loop.
#' @param ... Other arguments to be passed to the \code{compile} function.
#' @export
#' @examples # knit_watch('foo.Rnw', knit2pdf)
#'
#' # knit_watch('foo.Rmd', rmarkdown::render)
knit_watch = function(input, compile = knit, interval = 1, ...) {
mtime = function(...) file.info(...)[, 'mtime']
last_time = mtime(input)
updated = function() {
this_time = mtime(input)
on.exit(last_time <<- this_time, add = TRUE)
this_time > last_time
}
for (f in input) compile(f, ...)
while (TRUE) {
for (f in input[updated()]) compile(f, ...)
Sys.sleep(interval)
}
}