├── LICENSE
├── .gitignore
├── tests
├── empty.R
├── tests.R
└── tests.Rout.save
├── .Rbuildignore
├── R
├── package.R
├── utils.R
├── old.R
├── rmarkdown.R
├── render.R
└── rpubs.R
├── Makefile
├── NAMESPACE
├── inst
├── resources
│ ├── markdown.latex
│ ├── markdown.html
│ ├── prism-xcode.css
│ ├── default.css
│ ├── snap.css
│ └── snap.js
└── examples
│ └── render-options.R
├── man
├── markdown_options.Rd
├── smartypants.Rd
├── mark.Rd
├── markdown-package.Rd
├── renderMarkdown.Rd
├── html_format.Rd
└── rpubsUpload.Rd
├── markdown.Rproj
├── README.md
├── LICENSE.md
├── .github
└── workflows
│ └── R-CMD-check.yaml
├── DESCRIPTION
└── NEWS.md
/LICENSE:
--------------------------------------------------------------------------------
1 | YEAR: 2023
2 | COPYRIGHT HOLDER: Posit Software, PBC
3 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | .Rproj.user
2 | .Rhistory
3 | .RData
4 | .lvimrc
5 | src/*.o
6 | src/*.so
7 | src/*.rds
8 |
--------------------------------------------------------------------------------
/tests/empty.R:
--------------------------------------------------------------------------------
1 | library(markdown)
2 | f = tempfile()
3 | if (file.create(f)) {
4 | mark_html(f, template = FALSE)
5 | mark_html(f)
6 | unlink(f)
7 | }
8 |
--------------------------------------------------------------------------------
/tests/tests.R:
--------------------------------------------------------------------------------
1 | local({
2 | if (!file.exists(f <- '../inst/examples/render-options.R'))
3 | f = markdown:::pkg_file('examples', 'render-options.R')
4 | source(f, local = TRUE, echo = TRUE)
5 | })
6 |
--------------------------------------------------------------------------------
/.Rbuildignore:
--------------------------------------------------------------------------------
1 | .gitignore
2 | ^.*\.Rproj$
3 | ^\.Rproj\.user$
4 | ^\.git$
5 | ^tools$
6 | .lvimrc
7 | tests/.RData
8 | Makefile
9 | inst/RnwToMd.R
10 | ^\.travis\.yml$
11 | ^\.github$
12 | ^revdep$
13 | ^LICENSE\.md$
14 |
--------------------------------------------------------------------------------
/R/package.R:
--------------------------------------------------------------------------------
1 | #' Markdown rendering for R
2 | #'
3 | #' \pkg{Markdown} is a plain-text formatting syntax that can be converted to
4 | #' XHTML or other formats. This package provides wrapper functions (mainly
5 | #' [mark()]) based on the \pkg{commonmark} package.
6 | '_PACKAGE'
7 |
--------------------------------------------------------------------------------
/Makefile:
--------------------------------------------------------------------------------
1 | tests: tests/tests.Rout.save
2 |
3 | tests/tests.Rout.save: tests/tests.R inst/examples/render-options.R
4 | Rscript -e "Rd2roxygen::rab('.', install=TRUE)"
5 | rm markdown_*.tar.gz
6 | cd tests && R CMD BATCH --no-save --no-restore --no-timing tests.R tests.Rout.save
7 |
--------------------------------------------------------------------------------
/NAMESPACE:
--------------------------------------------------------------------------------
1 | # Generated by roxygen2: do not edit by hand
2 |
3 | export(html_format)
4 | export(latex_format)
5 | export(mark)
6 | export(mark_html)
7 | export(mark_latex)
8 | export(markdownToHTML)
9 | export(markdown_options)
10 | export(renderMarkdown)
11 | export(rpubsUpload)
12 | export(smartypants)
13 | import(utils)
14 |
--------------------------------------------------------------------------------
/inst/resources/markdown.latex:
--------------------------------------------------------------------------------
1 | \documentclass[$classoption$]{$documentclass$}
2 | \usepackage[T1]{fontenc}
3 | \usepackage{graphicx}
4 | $header-includes$
5 | \title{$title$}
6 | \author{$author$}
7 | \date{$date$}
8 | \begin{document}
9 | \maketitle
10 | $include-before$
11 | $body$
12 | $include-after$
13 | \end{document}
14 |
--------------------------------------------------------------------------------
/man/markdown_options.Rd:
--------------------------------------------------------------------------------
1 | % Generated by roxygen2: do not edit by hand
2 | % Please edit documentation in R/render.R
3 | \name{markdown_options}
4 | \alias{markdown_options}
5 | \title{Markdown rendering options}
6 | \usage{
7 | markdown_options()
8 | }
9 | \description{
10 | A wrapper function of \code{\link[litedown:markdown_options]{litedown::markdown_options()}}.
11 | }
12 |
--------------------------------------------------------------------------------
/markdown.Rproj:
--------------------------------------------------------------------------------
1 | Version: 1.0
2 |
3 | RestoreWorkspace: Default
4 | SaveWorkspace: Default
5 | AlwaysSaveHistory: Default
6 |
7 | EnableCodeIndexing: Yes
8 | UseSpacesForTab: Yes
9 | NumSpacesForTab: 2
10 | Encoding: UTF-8
11 |
12 | RnwWeave: knitr
13 | LaTeX: pdfLaTeX
14 |
15 | AutoAppendNewline: Yes
16 | StripTrailingWhitespace: Yes
17 |
18 | BuildType: Package
19 | PackageInstallArgs: -v && make -B -C
20 | PackageBuildArgs: -v && Rscript -e "Rd2roxygen::rab()"
21 | PackageCheckArgs: --as-cran
22 |
--------------------------------------------------------------------------------
/inst/resources/markdown.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | $title$
8 |
9 | $css$
10 |
11 | $header-includes$
12 |
13 |
14 |
15 | $include-before$
16 |
17 |
18 |
$title$
19 |
$author$
20 |
$date$
21 |
22 |
23 |
24 | $body$
25 |
26 |
27 | $include-after$
28 |
29 | $js$
30 |
31 |
32 |
33 |
--------------------------------------------------------------------------------
/man/smartypants.Rd:
--------------------------------------------------------------------------------
1 | % Generated by roxygen2: do not edit by hand
2 | % Please edit documentation in R/utils.R
3 | \name{smartypants}
4 | \alias{smartypants}
5 | \title{Convert some ASCII strings to HTML entities}
6 | \usage{
7 | smartypants(text)
8 | }
9 | \arguments{
10 | \item{text}{A character vector of the Markdown text.}
11 | }
12 | \value{
13 | A character vector of the transformed text.
14 | }
15 | \description{
16 | Transform ASCII strings \code{(c)} (copyright), \code{(r)} (registered trademark),
17 | \code{(tm)} (trademark), and fractions \code{n/m} into \emph{smart} typographic HTML
18 | entities.
19 | }
20 | \examples{
21 | cat(smartypants("1/2 (c)\n"))
22 | }
23 |
--------------------------------------------------------------------------------
/R/utils.R:
--------------------------------------------------------------------------------
1 | #' Convert some ASCII strings to HTML entities
2 | #'
3 | #' Transform ASCII strings `(c)` (copyright), `(r)` (registered trademark),
4 | #' `(tm)` (trademark), and fractions `n/m` into *smart* typographic HTML
5 | #' entities.
6 | #' @param text A character vector of the Markdown text.
7 | #' @return A character vector of the transformed text.
8 | #' @export
9 | #' @examples
10 | #' cat(smartypants("1/2 (c)\n"))
11 | smartypants = function(text) litedown:::smartypants(text)
12 |
13 | # get an option using a case-insensitive name
14 | get_option = function(name, default = NULL) {
15 | x = options()
16 | i = match(tolower(name), tolower(names(x)))
17 | i = i[!is.na(i)]
18 | if (length(i) == 0) default else x[[i[1]]]
19 | }
20 |
21 | pkg_file = function(...) {
22 | system.file(..., package = 'markdown', mustWork = TRUE)
23 | }
24 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Markdown rendering for R
2 |
3 |
4 |
5 | [](https://github.com/rstudio/markdown/actions/workflows/R-CMD-check.yaml)
6 | [](https://cran.r-project.org/package=markdown)
8 |
9 |
10 |
11 | ## Overview
12 |
13 | Markdown is a plain-text formatting syntax that can be converted to HTML or
14 | other formats. This package provides wrappers based on the
15 | [commonmark](https://github.com/r-lib/commonmark) package.
16 |
17 | Please note that this package is no longer actively developed. New development
18 | will continue only in [the **litedown**
19 | package](https://github.com/yihui/litedown), which is a new implementation of R
20 | Markdown.
21 |
22 | ## License
23 |
24 | The **markdown** package is licensed under MIT.
25 |
--------------------------------------------------------------------------------
/man/mark.Rd:
--------------------------------------------------------------------------------
1 | % Generated by roxygen2: do not edit by hand
2 | % Please edit documentation in R/render.R
3 | \name{mark}
4 | \alias{mark}
5 | \alias{mark_html}
6 | \alias{mark_latex}
7 | \title{Render Markdown to an output format}
8 | \usage{
9 | mark(
10 | file = NULL,
11 | output = NULL,
12 | text = NULL,
13 | format = c("html", "latex"),
14 | options = NULL,
15 | template = FALSE,
16 | meta = list()
17 | )
18 |
19 | mark_html(..., template = TRUE)
20 |
21 | mark_latex(..., template = TRUE)
22 | }
23 | \arguments{
24 | \item{file, output, text, options, meta}{Passed to \code{\link[litedown:mark]{litedown::mark()}}.}
25 |
26 | \item{format}{Output format name.}
27 |
28 | \item{template}{Whether to use a built-in template, or path to a custom
29 | template.}
30 |
31 | \item{...}{Arguments to be passed to \code{mark()}.}
32 | }
33 | \description{
34 | This is a wrapper function based on \code{\link[litedown:mark]{litedown::mark()}}. You should use
35 | \code{litedown::mark()} directly.
36 | }
37 |
--------------------------------------------------------------------------------
/LICENSE.md:
--------------------------------------------------------------------------------
1 | # MIT License
2 |
3 | Copyright (c) 2023 Posit Software, PBC
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/inst/resources/prism-xcode.css:
--------------------------------------------------------------------------------
1 | /**
2 | * Prism.s theme ported from highlight.js's xcode style
3 | */
4 | pre code {
5 | padding: 1em;
6 | }
7 | .token.comment {
8 | color: #007400;
9 | }
10 | .token.punctuation {
11 | color: #999;
12 | }
13 | .token.tag,
14 | .token.selector {
15 | color: #aa0d91;
16 | }
17 | .token.boolean,
18 | .token.number,
19 | .token.constant,
20 | .token.symbol {
21 | color: #1c00cf;
22 | }
23 | .token.property,
24 | .token.attr-name,
25 | .token.string,
26 | .token.char,
27 | .token.builtin {
28 | color: #c41a16;
29 | }
30 | .token.inserted {
31 | background-color: #ccffd8;
32 | }
33 | .token.deleted {
34 | background-color: #ffebe9;
35 | }
36 | .token.operator,
37 | .token.entity,
38 | .token.url,
39 | .language-css .token.string,
40 | .style .token.string {
41 | color: #9a6e3a;
42 | }
43 | .token.atrule,
44 | .token.attr-value,
45 | .token.keyword {
46 | color: #836c28;
47 | }
48 | .token.function,
49 | .token.class-name {
50 | color: #DD4A68;
51 | }
52 | .token.regex,
53 | .token.important,
54 | .token.variable {
55 | color: #5c2699;
56 | }
57 | .token.important,
58 | .token.bold {
59 | font-weight: bold;
60 | }
61 | .token.italic {
62 | font-style: italic;
63 | }
64 |
--------------------------------------------------------------------------------
/.github/workflows/R-CMD-check.yaml:
--------------------------------------------------------------------------------
1 | on:
2 | push:
3 | branches: [main, master]
4 | pull_request:
5 | branches: [main, master]
6 |
7 | name: R-CMD-check
8 |
9 | jobs:
10 | R-CMD-check:
11 | runs-on: ${{ matrix.config.os }}
12 |
13 | name: ${{ matrix.config.os }} (${{ matrix.config.r }})
14 |
15 | strategy:
16 | fail-fast: false
17 | matrix:
18 | config:
19 | - {os: macOS-latest, r: 'release'}
20 | - {os: windows-latest, r: 'release'}
21 | - {os: ubuntu-latest, r: 'devel', http-user-agent: 'release'}
22 | - {os: ubuntu-latest, r: 'release'}
23 | - {os: ubuntu-latest, r: 'oldrel-1'}
24 |
25 | env:
26 | GITHUB_PAT: ${{ secrets.GITHUB_TOKEN }}
27 | R_KEEP_PKG_SOURCE: yes
28 |
29 | steps:
30 | - uses: actions/checkout@v3
31 |
32 | - uses: r-lib/actions/setup-pandoc@v2
33 |
34 | - uses: r-lib/actions/setup-r@v2
35 | with:
36 | r-version: ${{ matrix.config.r }}
37 | http-user-agent: ${{ matrix.config.http-user-agent }}
38 | use-public-rspm: true
39 |
40 | - uses: r-lib/actions/setup-r-dependencies@v2
41 | with:
42 | extra-packages: any::rcmdcheck
43 | needs: check
44 |
45 | - uses: r-lib/actions/check-r-package@v2
46 | with:
47 | upload-snapshots: true
48 |
--------------------------------------------------------------------------------
/man/markdown-package.Rd:
--------------------------------------------------------------------------------
1 | % Generated by roxygen2: do not edit by hand
2 | % Please edit documentation in R/package.R
3 | \docType{package}
4 | \name{markdown-package}
5 | \alias{markdown}
6 | \alias{markdown-package}
7 | \title{Markdown rendering for R}
8 | \description{
9 | \pkg{Markdown} is a plain-text formatting syntax that can be converted to
10 | XHTML or other formats. This package provides wrapper functions (mainly
11 | \code{\link[=mark]{mark()}}) based on the \pkg{commonmark} package.
12 | }
13 | \seealso{
14 | Useful links:
15 | \itemize{
16 | \item \url{https://github.com/rstudio/markdown}
17 | \item Report bugs at \url{https://github.com/rstudio/markdown/issues}
18 | }
19 |
20 | }
21 | \author{
22 | \strong{Maintainer}: Yihui Xie \email{xie@yihui.name} (\href{https://orcid.org/0000-0003-0645-5666}{ORCID})
23 |
24 | Authors:
25 | \itemize{
26 | \item JJ Allaire
27 | \item Jeffrey Horner
28 | }
29 |
30 | Other contributors:
31 | \itemize{
32 | \item Henrik Bengtsson [contributor]
33 | \item Jim Hester [contributor]
34 | \item Yixuan Qiu [contributor]
35 | \item Kohske Takahashi [contributor]
36 | \item Adam November [contributor]
37 | \item Nacho Caballero [contributor]
38 | \item Jeroen Ooms [contributor]
39 | \item Thomas Leeper [contributor]
40 | \item Joe Cheng [contributor]
41 | \item Andrzej Oles [contributor]
42 | \item Posit Software, PBC [copyright holder, funder]
43 | }
44 |
45 | }
46 |
--------------------------------------------------------------------------------
/DESCRIPTION:
--------------------------------------------------------------------------------
1 | Package: markdown
2 | Type: Package
3 | Title: A thin wrapper of 'litedown' to render Markdown documents
4 | Version: 2.0.1
5 | Authors@R: c(
6 | person("Yihui", "Xie", role = c("aut", "cre"), email = "xie@yihui.name", comment = c(ORCID = "0000-0003-0645-5666")),
7 | person("JJ", "Allaire", role = "aut"),
8 | person("Jeffrey", "Horner", role = "aut"),
9 | person("Henrik", "Bengtsson", role = "ctb"),
10 | person("Jim", "Hester", role = "ctb"),
11 | person("Yixuan", "Qiu", role = "ctb"),
12 | person("Kohske", "Takahashi", role = "ctb"),
13 | person("Adam", "November", role = "ctb"),
14 | person("Nacho", "Caballero", role = "ctb"),
15 | person("Jeroen", "Ooms", role = "ctb"),
16 | person("Thomas", "Leeper", role = "ctb"),
17 | person("Joe", "Cheng", role = "ctb"),
18 | person("Andrzej", "Oles", role = "ctb"),
19 | person(given = "Posit Software, PBC", role = c("cph", "fnd"))
20 | )
21 | Description: Render Markdown to HTML/LaTeX. This package has been superseded by 'litedown'. Please call 'litedown::mark()' directly.
22 | Depends:
23 | R (>= 3.2.0)
24 | Imports:
25 | utils,
26 | xfun,
27 | litedown (>= 0.6)
28 | Suggests:
29 | knitr,
30 | rmarkdown (>= 2.18),
31 | yaml,
32 | RCurl
33 | License: MIT + file LICENSE
34 | URL: https://github.com/rstudio/markdown
35 | BugReports: https://github.com/rstudio/markdown/issues
36 | RoxygenNote: 7.3.2
37 | Encoding: UTF-8
38 | Roxygen: list(markdown = TRUE)
39 |
--------------------------------------------------------------------------------
/man/renderMarkdown.Rd:
--------------------------------------------------------------------------------
1 | % Generated by roxygen2: do not edit by hand
2 | % Please edit documentation in R/old.R
3 | \name{renderMarkdown}
4 | \alias{renderMarkdown}
5 | \alias{markdownToHTML}
6 | \title{Functions in \pkg{markdown} before \code{v1.3}.}
7 | \usage{
8 | renderMarkdown(file = NULL, output = NULL, ...)
9 |
10 | markdownToHTML(
11 | file = NULL,
12 | output = NULL,
13 | ...,
14 | options = getOption("markdown.HTML.options"),
15 | title = NULL,
16 | stylesheet = getOption("markdown.HTML.stylesheet"),
17 | header = getOption("markdown.HTML.header"),
18 | template = getOption("markdown.HTML.template", TRUE),
19 | fragment.only = FALSE,
20 | encoding = "UTF-8"
21 | )
22 | }
23 | \arguments{
24 | \item{file, output, ..., options, template}{Arguments to be passed to new
25 | functions.}
26 |
27 | \item{title, stylesheet, header}{Arguments to be passed to \verb{meta = list(title = , css = , }header-includes\verb{ = )}, which is passed to \code{\link[=mark_html]{mark_html()}}.}
28 |
29 | \item{fragment.only}{Whether to generate a fragment or a full HTML document.}
30 |
31 | \item{encoding}{Ignored.}
32 | }
33 | \description{
34 | These functions are kept in this package for backward-compatibility. They
35 | will not be removed in the foreseeable future, although we recommend that you
36 | use their new names instead: \code{renderMarkdown()} has become \code{\link[=mark]{mark()}}, and
37 | \code{markdownToHTML()} has become \code{\link[=mark_html]{mark_html()}}.
38 | }
39 | \keyword{internal}
40 |
--------------------------------------------------------------------------------
/R/old.R:
--------------------------------------------------------------------------------
1 | #' Functions in \pkg{markdown} before `v1.3`.
2 | #'
3 | #' These functions are kept in this package for backward-compatibility. They
4 | #' will not be removed in the foreseeable future, although we recommend that you
5 | #' use their new names instead: `renderMarkdown()` has become [mark()], and
6 | #' `markdownToHTML()` has become [mark_html()].
7 | #' @param file,output,...,options,template Arguments to be passed to new
8 | #' functions.
9 | #' @param title,stylesheet,header Arguments to be passed to `meta = list(title =
10 | #' , css = , `header-includes` = )`, which is passed to [mark_html()].
11 | #' @param fragment.only Whether to generate a fragment or a full HTML document.
12 | #' @param encoding Ignored.
13 | #' @export
14 | #' @keywords internal
15 | renderMarkdown = function(file = NULL, output = NULL, ...) mark(file, output = output, ...)
16 |
17 | #' @rdname renderMarkdown
18 | #' @export
19 | markdownToHTML = function(
20 | file = NULL, output = NULL, ..., options = getOption('markdown.HTML.options'),
21 | title = NULL, stylesheet = getOption('markdown.HTML.stylesheet'),
22 | header = getOption('markdown.HTML.header'),
23 | template = getOption('markdown.HTML.template', TRUE),
24 | fragment.only = FALSE, encoding = 'UTF-8'
25 | ) {
26 | if (fragment.only || 'fragment_only' %in% options) template = FALSE
27 | meta = list()
28 | meta$css = stylesheet
29 | meta$title = title
30 | meta$`header-includes` = header
31 | mark_html(file, output = output, ..., options = options, template = template, meta = meta)
32 | }
33 |
--------------------------------------------------------------------------------
/man/html_format.Rd:
--------------------------------------------------------------------------------
1 | % Generated by roxygen2: do not edit by hand
2 | % Please edit documentation in R/rmarkdown.R
3 | \name{html_format}
4 | \alias{html_format}
5 | \alias{latex_format}
6 | \title{R Markdown output formats}
7 | \usage{
8 | html_format(
9 | meta = NULL,
10 | template = NULL,
11 | options = NULL,
12 | keep_md = FALSE,
13 | keep_tex = FALSE,
14 | latex_engine = "xelatex"
15 | )
16 |
17 | latex_format(
18 | meta = NULL,
19 | template = NULL,
20 | options = NULL,
21 | keep_md = FALSE,
22 | keep_tex = FALSE,
23 | latex_engine = "xelatex"
24 | )
25 | }
26 | \arguments{
27 | \item{meta, template, options}{Arguments to be passed to \code{\link[=mark]{mark()}}.}
28 |
29 | \item{keep_md, keep_tex}{Whether to keep the intermediate \file{.md} and
30 | \file{.tex} files generated from \file{.Rmd}.}
31 |
32 | \item{latex_engine}{The LaTeX engine to compile \file{.tex} to \file{.pdf}.
33 | This argument and \code{keep_tex} are for \code{latex_format()} only, and ignored in
34 | \code{html_format()}.}
35 | }
36 | \description{
37 | Convenience functions for R Markdown v2 users.
38 | }
39 | \details{
40 | We refer to this \pkg{markdown} package plus \pkg{knitr} as \dQuote{R
41 | Markdown v1}, and the \pkg{rmarkdown} package as \dQuote{R Markdown v2}. The
42 | former uses \pkg{commonmark} to convert Markdown, and the latter uses Pandoc.
43 | However, the latter also accept custom converting tools in addition to
44 | Pandoc. The output formats here provide the custom converting function
45 | \code{\link[=mark]{mark()}} to \pkg{rmarkdown}, so that users can take advantage of
46 | \code{\link[rmarkdown:render]{rmarkdown::render()}} and the Knit button in RStudio. It is absolutely not
47 | necessary to rely on \pkg{rmarkdown}. The only point is convenience. If you
48 | do not use \code{rmarkdown::render()} or the Knit button, you can definitely just
49 | call \code{markdown::mark()} directly.
50 | }
51 |
--------------------------------------------------------------------------------
/inst/resources/default.css:
--------------------------------------------------------------------------------
1 | body {
2 | font-family: sans-serif;
3 | max-width: 800px;
4 | margin: auto;
5 | padding: 1em;
6 | line-height: 1.5;
7 | box-sizing: border-box;
8 | }
9 | body, .footnotes, code { font-size: .9em; }
10 | li li { font-size: .95em; }
11 | *, *:before, *:after {
12 | box-sizing: inherit;
13 | }
14 | pre, img { max-width: 100%; }
15 | pre, pre:hover {
16 | white-space: pre-wrap;
17 | word-break: break-all;
18 | }
19 | pre code {
20 | display: block;
21 | overflow-x: auto;
22 | }
23 | code { font-family: 'DejaVu Sans Mono', 'Droid Sans Mono', 'Lucida Console', Consolas, Monaco, monospace; }
24 | :not(pre) > code, code[class] { background-color: #F8F8F8; }
25 | code.language-undefined, pre > code:not([class]) {
26 | background-color: inherit;
27 | border: 1px solid #eee;
28 | }
29 | table {
30 | margin: auto;
31 | border-top: 1px solid #666;
32 | }
33 | table thead th { border-bottom: 1px solid #ddd; }
34 | th, td { padding: 5px; }
35 | thead, tfoot, tr:nth-child(even) { background: #eee; }
36 | blockquote {
37 | color: #666;
38 | margin: 0;
39 | padding-left: 1em;
40 | border-left: 0.5em solid #eee;
41 | }
42 | hr, .footnotes::before { border: 1px dashed #ddd; }
43 | .frontmatter { text-align: center; }
44 | #TOC .numbered li { list-style: none; }
45 | #TOC .numbered { padding-left: 0; }
46 | #TOC .numbered ul { padding-left: 1em; }
47 | table, .body h2 { border-bottom: 1px solid #666; }
48 | .body .appendix, .appendix ~ h2 { border-bottom-style: dashed; }
49 | .footnote-ref a::before { content: "["; }
50 | .footnote-ref a::after { content: "]"; }
51 | section.footnotes::before {
52 | content: "";
53 | display: block;
54 | max-width: 20em;
55 | }
56 |
57 | @media print {
58 | body {
59 | font-size: 12pt;
60 | max-width: 100%;
61 | }
62 | tr, img { page-break-inside: avoid; }
63 | }
64 | @media only screen and (min-width: 992px) {
65 | pre { white-space: pre; }
66 | }
67 |
--------------------------------------------------------------------------------
/inst/examples/render-options.R:
--------------------------------------------------------------------------------
1 | library(markdown)
2 |
3 | # toc example
4 | mkd <- c("# Header 1", "p1", "## Header 2", "p2")
5 |
6 | cat(mark(mkd, options = "+number_sections"))
7 | cat(mark(mkd, options = "+number_sections+toc"))
8 |
9 | # hard_wrap example
10 | cat(mark("foo\nbar\n"))
11 | cat(mark("foo\nbar\n", options = "hardbreaks"))
12 |
13 | # latex math example
14 | mkd <- c(
15 | "`$x$` is inline math $x$!", "", "Display style:", "", "$$x + y$$", "",
16 | "\\begin{eqnarray}
17 | a^{2}+b^{2} & = & c^{2}\\\\
18 | \\sin^{2}(x)+\\cos^{2}(x) & = & 1
19 | \\end{eqnarray}"
20 | )
21 |
22 | cat(mark(mkd))
23 | cat(mark(mkd, options = "-latex_math"))
24 |
25 | # tables example (need 4 spaces at beginning of line here)
26 | cat(mark("
27 | First Header | Second Header
28 | ------------- | -------------
29 | Content Cell | Content Cell
30 | Content Cell | Content Cell
31 | "))
32 |
33 | # but not here
34 | cat(mark("
35 | First Header | Second Header
36 | ------------- | -------------
37 | Content Cell | Content Cell
38 | Content Cell | Content Cell
39 | ", options = '-table'))
40 |
41 | # autolink example
42 | cat(mark("https://www.r-project.org/"))
43 | cat(mark("https://www.r-project.org/", options = "-autolink"))
44 |
45 | # strikethrough example
46 | cat(mark("~~awesome~~"))
47 | cat(mark("~~awesome~~", options = "-strikethrough"))
48 |
49 | # superscript and subscript examples
50 | cat(mark("2^10^"))
51 | cat(mark("2^10^", options = "-superscript"))
52 | cat(mark("H~2~O"))
53 | cat(mark("H~2~O", options = "-subscript"))
54 |
55 | # code blocks
56 | cat(mark('```r\n1 + 1;\n```'))
57 | cat(mark('```{.r}\n1 + 1;\n```'))
58 | cat(mark('```{.r .js}\n1 + 1;\n```'))
59 | cat(mark('```{.r .js #foo}\n1 + 1;\n```'))
60 | cat(mark('```{.r .js #foo style="color:red;"}\n1 + 1;\n```'))
61 | cat(mark('````\n```{r, echo=TRUE}\n1 + 1;\n```\n````'))
62 |
63 | # raw blocks
64 | cat(mark('```{=html}\nraw HTML
\n```'))
65 | cat(mark('```{=latex}\nraw HTML
\n```'))
66 |
67 | # skip_html tags
68 | mkd = '\n[Hello](#)'
69 | cat(mark(mkd))
70 | # TODO: wait for https://github.com/r-lib/commonmark/issues/15 to be fixed
71 | # cat(mark(mkd, options = "tagfilter"))
72 |
--------------------------------------------------------------------------------
/man/rpubsUpload.Rd:
--------------------------------------------------------------------------------
1 | % Generated by roxygen2: do not edit by hand
2 | % Please edit documentation in R/rpubs.R
3 | \name{rpubsUpload}
4 | \alias{rpubsUpload}
5 | \title{Upload an HTML file to RPubs}
6 | \usage{
7 | rpubsUpload(
8 | title,
9 | htmlFile,
10 | id = NULL,
11 | properties = list(),
12 | method = getOption("rpubs.upload.method", "auto")
13 | )
14 | }
15 | \arguments{
16 | \item{title}{The title of the document.}
17 |
18 | \item{htmlFile}{The path to the HTML file to upload.}
19 |
20 | \item{id}{If this upload is an update of an existing document then the id
21 | parameter should specify the document id to update. Note that the id is
22 | provided as an element of the list returned by successful calls to
23 | \code{rpubsUpload}.}
24 |
25 | \item{properties}{A named list containing additional document properties
26 | (RPubs doesn't currently expect any additional properties, this parameter
27 | is reserved for future use).}
28 |
29 | \item{method}{Method to be used for uploading. "internal" uses a plain http
30 | socket connection; "curl" uses the curl binary to do an https upload;
31 | "rcurl" uses the RCurl package to do an https upload; and "auto" uses the
32 | best available method searched for in the following order: "curl", "rcurl",
33 | and then "internal". The global default behavior can be configured by
34 | setting the \code{rpubs.upload.method} option (the default is "auto").}
35 | }
36 | \value{
37 | A named list. If the upload was successful then the list contains a
38 | \code{id} element that can be used to subsequently update the document as well
39 | as a \code{continueUrl} element that provides a URL that a browser should be
40 | opened to in order to complete publishing of the document. If the upload
41 | fails then the list contains an \code{error} element which contains an
42 | explanation of the error that occurred.
43 | }
44 | \description{
45 | This function uploads an HTML file to rpubs.com. If the upload succeeds a
46 | list that includes an \code{id} and \code{continueUrl} is returned. A browser should be
47 | opened to the \code{continueUrl} to complete publishing of the document. If an
48 | error occurs then a diagnostic message is returned in the \code{error} element of
49 | the list.
50 | }
51 | \examples{
52 | \dontrun{
53 | # upload a document
54 | result <- rpubsUpload("My document title", "Document.html")
55 | if (!is.null(result$continueUrl))
56 | browseURL(result$continueUrl) else stop(result$error)
57 |
58 | # update the same document with a new title
59 | updateResult <- rpubsUpload("My updated title", "Document.html", result$id)
60 | }
61 | }
62 |
--------------------------------------------------------------------------------
/R/rmarkdown.R:
--------------------------------------------------------------------------------
1 | output_format = function(to = 'html') {
2 | to
3 | function(
4 | meta = NULL, template = NULL, options = NULL, keep_md = FALSE,
5 | keep_tex = FALSE, latex_engine = 'xelatex'
6 | ) {
7 | opts = rmarkdown::pandoc_options(
8 | to = to, keep_tex = keep_tex, latex_engine = latex_engine, args = '--template'
9 | )
10 | opts$convert_fun = function(input, output, ...) {
11 | mark(input, output, NULL, to, options, template, meta)
12 | }
13 | rmarkdown::output_format(
14 | NULL, opts, keep_md = keep_md,
15 | clean_supporting = 'local' %in% litedown:::normalize_options(options)[['embed_resources']]
16 | )
17 | }
18 | }
19 |
20 | #' R Markdown output formats
21 | #'
22 | #' Convenience functions for R Markdown v2 users.
23 | #'
24 | #' We refer to this \pkg{markdown} package plus \pkg{knitr} as \dQuote{R
25 | #' Markdown v1}, and the \pkg{rmarkdown} package as \dQuote{R Markdown v2}. The
26 | #' former uses \pkg{commonmark} to convert Markdown, and the latter uses Pandoc.
27 | #' However, the latter also accept custom converting tools in addition to
28 | #' Pandoc. The output formats here provide the custom converting function
29 | #' [mark()] to \pkg{rmarkdown}, so that users can take advantage of
30 | #' [rmarkdown::render()] and the Knit button in RStudio. It is absolutely not
31 | #' necessary to rely on \pkg{rmarkdown}. The only point is convenience. If you
32 | #' do not use `rmarkdown::render()` or the Knit button, you can definitely just
33 | #' call `markdown::mark()` directly.
34 | #' @param meta,template,options Arguments to be passed to [mark()].
35 | #' @param keep_md,keep_tex Whether to keep the intermediate \file{.md} and
36 | #' \file{.tex} files generated from \file{.Rmd}.
37 | #' @param latex_engine The LaTeX engine to compile \file{.tex} to \file{.pdf}.
38 | #' This argument and `keep_tex` are for `latex_format()` only, and ignored in
39 | #' `html_format()`.
40 | #' @export
41 | html_format = output_format('html')
42 |
43 | #' @rdname html_format
44 | #' @export
45 | latex_format = output_format('latex')
46 |
47 | # compatibility layers to rmarkdown::[html|pdf]_document
48 | html_document = function(...) do.call(html_format, map_args(...))
49 | html_vignette = function(...) html_document(...)
50 | pdf_document = function(...) do.call(latex_format, map_args(...))
51 |
52 | map_args = function(...) {
53 | do.call(litedown:::map_args, convert_yn(list(...)))
54 | }
55 |
56 | convert_yn = function(x) {
57 | lapply(x, function(z) {
58 | if (is.list(z)) convert_yn(z) else if (identical(z, 'yes')) TRUE else
59 | if (identical(z, 'no')) FALSE else z
60 | })
61 | }
62 |
--------------------------------------------------------------------------------
/inst/resources/snap.css:
--------------------------------------------------------------------------------
1 | :root { --slide-width: 100%; }
2 | html { scroll-snap-type: y mandatory; }
3 | th, td { padding: .2em .5em; }
4 | .slide {
5 | padding: 1em;
6 | position: relative;
7 | }
8 | .slide > h2, .slide > h3 { margin-top: unset; }
9 | body {
10 | max-width: fit-content;
11 | padding: 0;
12 | }
13 | a { color: #eb4a47; }
14 | :not(pre) > code { background-color: #fdfded; }
15 | #TOC { columns: 2; }
16 | #TOC::before {
17 | font-size: 1.3em;
18 | font-weight: bold;
19 | display: block;
20 | border-bottom: 1px solid #666;
21 | }
22 | .frontmatter, .middle {
23 | display: flex;
24 | justify-content: center;
25 | flex-direction: column;
26 | }
27 | .page-number, .timer {
28 | position: absolute;
29 | bottom: 0;
30 | opacity: .5;
31 | font: .7em monospace;
32 | }
33 | .page-number { right: 0; }
34 | .timer { left: 0; }
35 | .inverse {
36 | background-color: #eee;
37 | filter: invert(1);
38 | }
39 | .fade {
40 | background: repeating-linear-gradient(135deg, white, white 30px, #ddd 32px, #ddd 32px);
41 | opacity: 0.6;
42 | }
43 | .center { text-align: center; }
44 | .slide-container h2 .section-number {
45 | display: inline-block;
46 | background-color: #666;
47 | color: white;
48 | padding: 0 .1em;
49 | margin-right: .3em;
50 | }
51 | .overview {
52 | font-size: .8em;
53 | max-width: none;
54 | }
55 | .overview .slide {
56 | min-height: unset;
57 | scroll-snap-align: unset;
58 | }
59 | .overview .slide-container {
60 | display: flex;
61 | flex-wrap: wrap;
62 | justify-content: space-evenly;
63 | }
64 | .overview .slide-container .slide {
65 | width: var(--slide-width);
66 | border: 1px dotted #ccc;
67 | margin-bottom: 0.5em;
68 | }
69 | .mirrored { transform: scale(-1, 1); }
70 | .spacer { height: 50vh; }
71 | .overview .timer, .overview .spacer { display: none; }
72 | .overview .footnotes { position: unset; }
73 | html:fullscreen::-webkit-scrollbar { display: none; }
74 | html:fullscreen {
75 | -ms-overflow-style: none;
76 | scrollbar-width: none;
77 | }
78 | @media (min-width: 992px) {
79 | :root { --slide-width: 49%; }
80 | body { font-size: 2em; }
81 | .slide {
82 | min-height: 100vh;
83 | scroll-snap-align: start;
84 | }
85 | li li { font-size: .9em; }
86 | .footnotes {
87 | position: absolute;
88 | bottom: 1em;
89 | font-size: .8em;
90 | }
91 | }
92 | @media (min-width: 1400px) {
93 | :root { --slide-width: 33%; }
94 | }
95 | @media (min-width: 1800px) {
96 | :root { --slide-width: 24.67%; }
97 | }
98 | @media print, (max-width: 991.98px) {
99 | .timer, .spacer { display: none; }
100 | }
101 |
--------------------------------------------------------------------------------
/R/render.R:
--------------------------------------------------------------------------------
1 | #' Render Markdown to an output format
2 | #'
3 | #' This is a wrapper function based on [litedown::mark()]. You should use
4 | #' `litedown::mark()` directly.
5 | #' @param file,output,text,options,meta Passed to [litedown::mark()].
6 | #' @param format Output format name.
7 | #' @param template Whether to use a built-in template, or path to a custom
8 | #' template.
9 | #' @import utils
10 | #' @export
11 | mark = function(
12 | file = NULL, output = NULL, text = NULL, format = c('html', 'latex'),
13 | options = NULL, template = FALSE, meta = list()
14 | ) {
15 | format = format[1]
16 | opts = options(stats::setNames(
17 | list(get_option(sprintf('markdown.%s.template', format), template)),
18 | sprintf('litedown.%s.template', format)
19 | ))
20 | on.exit(options(opts), add = TRUE)
21 | if (is.null(output)) output = format
22 | # check if bibutils is available for bibliography, and convert yes/no to Boolean
23 | if (litedown:::is_file(file)) {
24 | is_check = xfun::is_R_CMD_check()
25 | parts = xfun::yaml_body(xfun::read_utf8(file), parse = FALSE)
26 | yaml = parts$yaml
27 | if (length(i <- grep('^bibliography:\\s+', yaml)) && !xfun::loadable('rbibutils')) {
28 | if (is_check) {
29 | yaml = yaml[-i]
30 | } else stop(
31 | 'Detected bibliography in YAML (', file, ') but the rbibutils package is ',
32 | 'unavailable. Please make sure it is installed (and declared in Suggests ',
33 | 'if bibliography is used in package vignettes).'
34 | )
35 | }
36 | if (length(i <- grep(':\\s+(yes|no)\\s*$', yaml))) {
37 | if (is_check) {
38 | yaml[i] = sub('yes\\s*$', 'true', yaml[i])
39 | yaml[i] = sub('no\\s*$', 'false', yaml[i])
40 | } else stop(
41 | 'Detected yes/no in YAML(', file, '). Please replace them with true/false.'
42 | )
43 | }
44 | if (length(i <- grep('^\\s*- ', yaml)) && xfun::try_error(xfun::taml_load(yaml))) {
45 | yaml[i] = sub('^(\\s*)- ', '\\1 ', yaml[i])
46 | if (xfun::try_error(xfun::taml_load(yaml))) stop(
47 | 'Cannot parse YAML (', file, '). See https://yihui.org/litedown/#sec:yaml-syntax for the syntax.'
48 | ) else if (!is_check) stop(
49 | "Detected items starting with '- ' in YAML(", file, '). The syntax is ',
50 | 'not supported by litedown: https://yihui.org/litedown/#sec:yaml-syntax'
51 | )
52 | }
53 | if (is_check) {
54 | xfun::write_utf8(c('---', yaml, '---', parts$body), file)
55 | }
56 | }
57 | res = litedown::mark(file, output, text, options, meta)
58 | if (format == 'html') {
59 | # remove sec/chp prefix in section IDs
60 | res = gsub('()$', '\\1\n', res)
63 | }
64 | as.character(res)
65 | }
66 |
67 | #' @rdname mark
68 | #' @param ... Arguments to be passed to `mark()`.
69 | #' @export
70 | mark_html = function(..., template = TRUE) {
71 | mark(..., format = 'html', template = template)
72 | }
73 |
74 | #' @export
75 | #' @rdname mark
76 | mark_latex = function(..., template = TRUE) {
77 | mark(..., format = 'latex', template = template)
78 | }
79 |
80 | #' Markdown rendering options
81 | #'
82 | #' A wrapper function of [litedown::markdown_options()].
83 | #' @export
84 | markdown_options = function() litedown::markdown_options()
85 |
--------------------------------------------------------------------------------
/tests/tests.Rout.save:
--------------------------------------------------------------------------------
1 |
2 | R version 4.4.3 (2025-02-28) -- "Trophy Case"
3 | Copyright (C) 2025 The R Foundation for Statistical Computing
4 | Platform: aarch64-apple-darwin20
5 |
6 | R is free software and comes with ABSOLUTELY NO WARRANTY.
7 | You are welcome to redistribute it under certain conditions.
8 | Type 'license()' or 'licence()' for distribution details.
9 |
10 | Natural language support but running in an English locale
11 |
12 | R is a collaborative project with many contributors.
13 | Type 'contributors()' for more information and
14 | 'citation()' on how to cite R or R packages in publications.
15 |
16 | Type 'demo()' for some demos, 'help()' for on-line help, or
17 | 'help.start()' for an HTML browser interface to help.
18 | Type 'q()' to quit R.
19 |
20 | > local({
21 | + if (!file.exists(f <- '../inst/examples/render-options.R'))
22 | + f = markdown:::pkg_file('examples', 'render-options.R')
23 | + source(f, local = TRUE, echo = TRUE)
24 | + })
25 |
26 | > library(markdown)
27 |
28 | > mkd <- c("# Header 1", "p1", "## Header 2", "p2")
29 |
30 | > cat(mark(mkd, options = "+number_sections"))
31 |
32 | p1
33 |
34 | p2
35 |
36 | > cat(mark(mkd, options = "+number_sections+toc"))
37 |
46 |
47 | p1
48 |
49 | p2
50 |
51 | > cat(mark("foo\nbar\n"))
52 | foo
53 | bar
54 |
55 | > cat(mark("foo\nbar\n", options = "hardbreaks"))
56 | foo
57 | bar
58 |
59 | > mkd <- c("`$x$` is inline math $x$!", "", "Display style:",
60 | + "", "$$x + y$$", "", "\\begin{eqnarray}\na^{2}+b^{2} & = & c^{2}\\\\\n\\sin^{2}(x ..." ... [TRUNCATED]
61 |
62 | > cat(mark(mkd))
63 | $x$ is inline math \(x\)!
64 | Display style:
65 | $$x + y$$
66 | \begin{eqnarray}
67 | a^{2}+b^{2} & = & c^{2}\\
68 | \sin^{2}(x)+\cos^{2}(x) & = & 1
69 | \end{eqnarray}
70 |
71 | > cat(mark(mkd, options = "-latex_math"))
72 | $x$ is inline math $x$!
73 | Display style:
74 | $$x + y$$
75 | \begin{eqnarray}
76 | a^{2}+b^{2} & = & c^{2}\
77 | \sin^{2}(x)+\cos^{2}(x) & = & 1
78 | \end{eqnarray}
79 |
80 | > cat(mark("\nFirst Header | Second Header\n------------- | -------------\nContent Cell | Content Cell\nContent Cell | Content Cell\n"))
81 |
82 |
83 |
84 | | First Header |
85 | Second Header |
86 |
87 |
88 |
89 |
90 | | Content Cell |
91 | Content Cell |
92 |
93 |
94 | | Content Cell |
95 | Content Cell |
96 |
97 |
98 |
99 |
100 | > cat(mark("\nFirst Header | Second Header\n------------- | -------------\nContent Cell | Content Cell\nContent Cell | Content Cell\n",
101 | + opti .... [TRUNCATED]
102 | First Header | Second Header
103 | ———–– | ———––
104 | Content Cell | Content Cell
105 | Content Cell | Content Cell
106 |
107 | > cat(mark("https://www.r-project.org/"))
108 | https://www.r-project.org/
109 |
110 | > cat(mark("https://www.r-project.org/", options = "-autolink"))
111 | https://www.r-project.org/
112 |
113 | > cat(mark("~~awesome~~"))
114 | awesome
115 |
116 | > cat(mark("~~awesome~~", options = "-strikethrough"))
117 | ~~awesome~~
118 |
119 | > cat(mark("2^10^"))
120 | 210
121 |
122 | > cat(mark("2^10^", options = "-superscript"))
123 | 2^10^
124 |
125 | > cat(mark("H~2~O"))
126 | H2O
127 |
128 | > cat(mark("H~2~O", options = "-subscript"))
129 | H~2~O
130 |
131 | > cat(mark("```r\n1 + 1;\n```"))
132 | 1 + 1;
133 |
134 |
135 | > cat(mark("```{.r}\n1 + 1;\n```"))
136 | 1 + 1;
137 |
138 |
139 | > cat(mark("```{.r .js}\n1 + 1;\n```"))
140 | 1 + 1;
141 |
142 |
143 | > cat(mark("```{.r .js #foo}\n1 + 1;\n```"))
144 | 1 + 1;
145 |
146 |
147 | > cat(mark("```{.r .js #foo style=\"color:red;\"}\n1 + 1;\n```"))
148 | 1 + 1;
149 |
150 |
151 | > cat(mark("````\n```{r, echo=TRUE}\n1 + 1;\n```\n````"))
152 | ```{r, echo=TRUE}
153 | 1 + 1;
154 | ```
155 |
156 |
157 | > cat(mark("```{=html}\nraw HTML
\n```"))
158 | raw HTML
159 |
160 | > cat(mark("```{=latex}\nraw HTML
\n```"))
161 |
162 | > mkd = "\n[Hello](#)"
163 |
164 | > cat(mark(mkd))
165 |
166 | Hello
167 | >
168 |
--------------------------------------------------------------------------------
/inst/resources/snap.js:
--------------------------------------------------------------------------------
1 | (function(d) {
2 | let p = d.body; // container of slides; assume for now
3 | const s1 = ':scope > hr:not([class])', s2 = ':scope > h2';
4 | // find a container that has at least n "slides"
5 | function findContainer(s, n = 1) {
6 | if (p.querySelectorAll(s).length >= n) return true;
7 | // if body doesn't contain headings or
s, look into children
8 | for (let i = 0; i < p.children.length; i++) {
9 | if (p.children[i].querySelectorAll(s).length >= n) {
10 | p = p.children[i]; break;
11 | }
12 | }
13 | return false;
14 | }
15 | function newEl(tag, cls) {
16 | const el = d.createElement(tag);
17 | if (cls) el.className = cls;
18 | return el;
19 | }
20 | if (!findContainer(s1, 3)) {
21 | // if not enough
s found in children; look for instead
22 | if (p.tagName === 'BODY') {
23 | // not enough h2 found, this page is not appropriate for slides
24 | if (!findContainer(s2) && p.tagName === 'BODY') return;
25 | p.querySelectorAll(s2).forEach(h2 => h2.before(newEl('hr')));
26 | }
27 | }
28 | p.classList.add('slide-container');
29 | // add 'slide' class to the frontmatter div and toc
30 | ['.frontmatter', '#TOC'].forEach(s => {
31 | d.body.querySelector(s)?.classList.add('slide');
32 | });
33 |
34 | function newSlide(s) {
35 | return (s?.innerText === '') ? s : newEl('div', 'slide');
36 | }
37 | function isSep(el) {
38 | return el.tagName === 'HR' && el.attributes.length === 0;
39 | }
40 | let el = p.firstElementChild; if (isSep(el)) el.remove();
41 | el = p.firstElementChild; if (!el) return;
42 | let s = newSlide(); el.before(s);
43 | while (true) {
44 | let el = s.nextSibling;
45 | if (!el) break;
46 | // remove slide separators (
) and create new slide
47 | if (isSep(el)) {
48 | s = newSlide(s);
49 | el.before(s); el.remove();
50 | } else if (el.classList?.contains('slide')) {
51 | s = newSlide(s);
52 | el.after(s);
53 | } else {
54 | s.append(el);
55 | }
56 | }
57 | function setAttr(el, attr) {
58 | const m = newEl('div');
59 | m.innerHTML = `
`;
60 | const attrs = m.firstElementChild.attributes;
61 | for (let i = 0; i < attrs.length; i++) {
62 | let a = attrs[i];
63 | el.setAttribute(a.name, a.value);
64 | }
65 | m.remove();
66 | }
67 | const slides = d.querySelectorAll('div.slide'), N = slides.length,
68 | tm = d.querySelector('span.timer'), fn = d.querySelector('.footnotes');
69 | slides.forEach((s, i) => {
70 | // append footnotes
71 | if (fn) s.querySelectorAll('.footnote-ref > a[href^="#fn"]').forEach(a => {
72 | const li = fn.querySelector('li' + a.getAttribute('href'));
73 | if (!li) return;
74 | let f = s.querySelector('section.footnotes');
75 | if (!f) {
76 | f = newEl('section', 'footnotes'); s.append(f);
77 | }
78 | f.append(li);
79 | li.firstElementChild?.insertAdjacentHTML('afterbegin', `[${a.innerHTML}] `);
80 | li.outerHTML = li.innerHTML;
81 | });
82 | // add a timer
83 | s.append(tm ? tm.cloneNode() : newEl('span', 'timer'));
84 | // add page numbers
85 | const n = newEl('span', 'page-number');
86 | n.innerText = i + 1 + '/' + N;
87 | n.onclick = e => location.hash = i + 1;
88 | s.append(n);
89 | // apply slide attributes in
90 | for (let k in s.childNodes) {
91 | const node = s.childNodes[k];
92 | if (node.nodeType !== Node.COMMENT_NODE) continue;
93 | let t = node.textContent;
94 | if (!/^#/.test(t)) continue;
95 | t = t.replace(/^#/, '');
96 | const r = /[\s\n]class="([^"]+)"/, m = t.match(r);
97 | if (m) {
98 | t = t.replace(r, '').trim();
99 | s.className += ' ' + m[1];
100 | }
101 | if (t) setAttr(s, t);
102 | break;
103 | }
104 | s.classList.contains('extend') && s.append(newEl('div', 'spacer fade'));
105 | location.hash === ('#' + (i + 1)) && s.scrollIntoView();
106 | s.addEventListener('click', e => {
107 | if (!e.altKey) return;
108 | d.body.classList.toggle('overview');
109 | setTimeout(() => e.target.scrollIntoView(), 100);
110 | });
111 | });
112 | [...d.querySelectorAll('a.footnote-backref'), fn, tm].forEach(el => el?.remove());
113 | const tms = d.querySelectorAll('span.timer'), t1 = 1000 * tms[0].dataset.total;
114 | let t0;
115 | function startTimers() {
116 | t0 = new Date();
117 | setInterval(setTimers, 1000);
118 | }
119 | function setTimers() {
120 | let t = (new Date() - t0);
121 | if (t1) t = t1 - t;
122 | const t2 = new Date(Math.abs(t)).toISOString().substr(11, 8).replace(/^00:/, '');
123 | tms.forEach(el => {
124 | el.innerText = t2;
125 | if (t < 0) el.style.display = el.style.display === 'none' ? '' : 'none';
126 | });
127 | }
128 | // press f for fullscreen mode
129 | d.addEventListener('keyup', (e) => {
130 | if (e.target !== d.body) return;
131 | e.key === 'f' && d.documentElement.requestFullscreen();
132 | e.key === 'o' && d.body.classList.toggle('overview');
133 | e.key === 'm' && d.body.classList.toggle('mirrored');
134 | sessionStorage.setItem('body-class', d.body.className);
135 | });
136 | // start timer on fullscreen
137 | d.onfullscreenchange = (e) => d.fullscreenElement && !t0 && startTimers();
138 | tms.forEach(el => el.addEventListener('click', e => startTimers()));
139 | // restore previously saved body class
140 | const bc = sessionStorage.getItem('body-class');
141 | if (bc) d.body.className += ' ' + bc;
142 | })(document);
143 |
--------------------------------------------------------------------------------
/NEWS.md:
--------------------------------------------------------------------------------
1 | # CHANGES IN markdown VERSION 2.0
2 |
3 | - The core function `mark()` is a thin wrapper of `litedown::mark()` now. Users are recommended to call **litedown** directly instead of through the wrapper.
4 |
5 | # CHANGES IN markdown VERSION 1.13
6 |
7 | - Cleaned `sourcepos` records when they come from metadata (thanks, @dmurdoch, #111).
8 |
9 | - The **markdown** package is in the maintenance-only mode now. It is feature-complete, and will receive no updates except for fixing CRAN problems. New development will continue only in **litedown**: .
10 |
11 | # CHANGES IN markdown VERSION 1.12
12 |
13 | - Provided three internal functions `html_document`, `html_vignette`, and `pdf_document` as compatibility layers to functions of the same names in the **rmarkdown** package (thanks, @jangorecki, #108).
14 |
15 | - The default HTML template no longer wraps meta variables `include-before` and `include-after` inside ``, because their values may contain incomplete HTML tags, e.g., `include-before = ''` and `include-after = '
'`.
16 |
17 | # CHANGES IN markdown VERSION 1.11
18 |
19 | - Verbatim code blocks of the form ```` ```{lang attr1 attr2 ...} ```` were not correctly rendered.
20 |
21 | # CHANGES IN markdown VERSION 1.10
22 |
23 | - Raw blocks (```` ```{=lang} ````) were broken in the previous version when the support for code block attributes was added.
24 |
25 | # CHANGES IN markdown VERSION 1.9
26 |
27 | - Added support for attributes on fenced code blocks, e.g., ```` ```{.lang .class2 #id attr="value"}```` (thanks, @thothal, #106).
28 |
29 | - Fixed the bug that the option `number_sections: true` doesn't work for HTML output when then input contains certain Unicode characters (thanks, @fyuniv, #104).
30 |
31 | - Added support for rendering HTML Widgets such as **ggplotly** (thanks, @fyuniv, #105).
32 |
33 | # CHANGES IN markdown VERSION 1.8
34 |
35 | - Fixed the superfluous warning about path lengths in `mark_html()` (thanks, @kenjisato, #103).
36 |
37 | # CHANGES IN markdown VERSION 1.7
38 |
39 | - The `file` argument of `mark()` will be treated as a file path only if the file exists and the value is not wrapped in `I()`. Previously, it would be treated as a file path when it has a file extension, which could lead to confusing errors like #100 (thanks, @LukasWallrich).
40 |
41 | - When there are emojis in the text, `mark()` may fail to identify and embed web resources (thanks, @tdhock, yihui/knitr#2254).
42 |
43 | # CHANGES IN markdown VERSION 1.6
44 |
45 | - Added support for footnotes, fenced `Div`s, section numbers, `{}` attributes for images/headings/fenced `Div`s, and appendices. See `vignette('intro', package = 'markdown')` for details.
46 |
47 | - A lot of enhancements to the HTML slides format. See `vignette('slides', package = 'markdown')` for details.
48 |
49 | - Added `vignette('article', package = 'markdown')` to demonstrate how to write an HTML article.
50 |
51 | - If the input to `mark()` is a file, the output will also be a file by default. Previously the output would be text. If you want `mark()` to return text output when the input is a file, you may specify the argument `output = NULL`.
52 |
53 | - The Markdown option `base64_images` has been renamed to `embed_resources`. This option can take two possible values, `"local"` and `"https"`, meaning whether to embed local and/or web (https) resources. You can specify none, either, or both of them. See `vignette('intro', package = 'markdown')` for details.
54 |
55 | - Removed the option `standalone` from the list of Markdown options. Please use the argument `template = TRUE/FALSE` of `mark()` instead. The option `standalone = TRUE` was equivalent to `template = TRUE`.
56 |
57 | - Added the option `auto_identifiers` (enabled by default) to automatically add IDs to headings, e.g., `# Hello world!` will be converted to `Hello world!
`. You can certainly override the automatic ID by providing an ID manually via the `{#id}` attribute, e.g., `# Hello world! {#hello}`.
58 |
59 | - Renamed the `mathjax` option to `js_math` to allow for other JS math libraries. The default library was changed from MathJax to KaTeX. To continue using MathJax, you may set `js_math: mathjax`.
60 |
61 | - Removed the option `mathjax_embed` from the list of Markdown options. To embed the MathJax library, enable `"https"` in the `embed_resources` option instead. Note that only MathJax v3 can be partially embedded, and lower versions cannot.
62 |
63 | - Renamed the option `highlight_code` to `js_highlight`, and added support for an alternative syntax highlighting JS library Prism.js, which became the default. To continue using the old default `highlight.js`, you may set the `js_highlight` option to `highlight`.
64 |
65 | - The default version of MathJax has been changed from v2 to v3.
66 |
67 | - The default version of highlight.js has been changed from 11.6.0 to 11.7.0, and the default style has been switched from `github` to `xcode`.
68 |
69 | # CHANGES IN markdown VERSION 1.5
70 |
71 | - Values of meta variables `title`, `author`, and `date` (if provided) will be transformed to the target output format before they are passed into templates.
72 |
73 | - Fixed the bug that the default CSS was not added to HTML output.
74 |
75 | - Removed dependency on the **mime** package.
76 |
77 | - Added experimental support for HTML slides: `markdown::mark_html(..., meta = list(css = c('default', 'slides'), js = 'slides'))`. If you prefer knitting `Rmd` documents in RStudio, you may use the output format:
78 |
79 | ```yaml
80 | output:
81 | markdown::html_format:
82 | meta:
83 | css: [default, slides]
84 | js: [slides]
85 | ```
86 |
87 | See https://yihui.org/en/2023/01/minimal-r-markdown/ for a demo.
88 |
89 | # CHANGES IN markdown VERSION 1.4
90 |
91 | - Empty `\title{}` in LaTeX output will be removed (along with `\maketitle`).
92 |
93 | - highlight.js is loaded from https://www.jsdelivr.com/package/gh/highlightjs/cdn-release by default now. This means more languages are supported (not only R), but also means syntax-highlighting will not work offline at the moment (it will be improved in future).
94 |
95 | - MathJax failed to load in the previous version. The bug has been fixed now.
96 |
97 | - Removed the function `markdownExtensions()`.
98 |
99 | # CHANGES IN markdown VERSION 1.3
100 |
101 | - Switched the underlying Markdown rendering engine from the C library **sundown** (which has been deprecated for a decade) to the R package **commonmark** (thanks, @jeroen, yihui/knitr#1329).
102 |
103 | - The functions `renderMarkdown()` and `markdownToHTML()` have been renamed to `mark()` and `mark_html()`, respectively. The old names are still kept in this package for backward-compatibility.
104 |
105 | - Removed the arguments `stylesheet` and `fragment.only` in `mark_html()`. For `stylesheet`, please use the argument `meta = list(css = ...)` to provide the CSS stylesheet. For `fragment.only`, please use `mark_html(template = FALSE)` or `mark_html(options = '-standalone')` instead of `fragment.only = TRUE`. Currently these old arguments are still accepted internally, but may be deprecated and dropped in the long run.
106 |
107 | - The `file` argument of `mark()` and `mark_html()` can also take a character vector of Markdown text now.
108 |
109 | - Removed functions `rendererExists()`, `rendererOutputType()`, and `registeredRenderer()`. They were primarily for internal use.
110 |
111 | - Deprecated the function `markdownExtensions()`. All extensions should be specified via the `options` argument of functions like `mark()`, e.g., `mark(options = '+table+tasklist')`. See all options on the help page `?markdown::markdown_options`.
112 |
113 | - Renamed `markdownHTMLOptions()` to `markdown_options()`.
114 |
115 | # CHANGES IN markdown VERSION 1.2
116 |
117 | - Fixed the warnings "a function declaration without a prototype is deprecated in all versions of C" (#94).
118 |
119 | # CHANGES IN markdown VERSION 1.1
120 |
121 | ## MAJOR CHANGES
122 |
123 | - renderMarkdown() and markdownToHTML() will signal an error if the input file is not encoded in "UTF-8".
124 |
125 | # CHANGES IN markdown VERSION 1.0
126 |
127 | ## MAJOR CHANGES
128 |
129 | - The default value of the encoding argument of renderMarkdown() and markdownToHTML() has been changed from getOption("encoding") to "UTF-8". The encoding of the input file will always be assumed to be UTF-8.
130 |
131 | - markdownToHTML() will return a character vector encoded in UTF-8 (instead of the system's native encoding) when not writing to an output file.
132 |
133 | # CHANGES IN markdown VERSION 0.9
134 |
135 | ## BUG FIXES
136 |
137 | - Fixed clang-UBSAN and valgrind issues (thanks, @yixuan, #92).
138 |
139 | # CHANGES IN markdown VERSION 0.8
140 |
141 | ## MINOR CHANGES
142 |
143 | - the MathJax CDN URL was replaced by https://www.bootcdn.cn/mathjax/
144 |
145 | ## BUG FIXES
146 |
147 | - fixed https://github.com/rstudio/htmltools/issues/30: markdownToHTML() did not work with empty files (thanks, @VermillionAzure)
148 |
149 | # CHANGES IN markdown VERSION 0.7.7
150 |
151 | ## BUG FIXES
152 |
153 | - renderMarkdown() works now even if text = character(0) or ""
154 |
155 | - added an `encoding` argument to renderMarkdown() since multi-byte characters in renderMarkdown() did not work on Windows (thanks, Kohske Takahashi, #63)
156 |
157 | - fixed #64: invalid 'n' argument in rpubsUpload() (thanks, Wouter van Atteveldt)
158 |
159 | ## MAJOR CHANGES
160 |
161 | - if renderMarkdown() returns a character vector, it will be marked with the UTF-8 encoding if it contains multi-byte characters
162 |
163 | # CHANGES IN markdown VERSION 0.7.4
164 |
165 | ## NEW FEATURES
166 |
167 | - when an image is the only element of its parent node in the HTML output document, it is automatically centered on the page
168 |
169 | ## MINOR CHANGES
170 |
171 | - images that have already been base64 encoded will not be encoded again (#61)
172 |
173 | - the URL of the MathJax CDN was updated to cdn.mathjax.org
174 |
175 | # CHANGES IN markdown VERSION 0.7.2
176 |
177 | ## BUG FIXES
178 |
179 | - fixed #60: MathJax may be included even if it is unnecessary when syntax highlighting is enabled (thanks, @aoles)
180 |
181 | - fixed a bug which may hang R when building R Markdown vignettes in a wrong locale (thanks, Dan Tenenbaum, yihui/knitr#782)
182 |
183 | # CHANGES IN markdown VERSION 0.7
184 |
185 | ## BUG FIXES
186 |
187 | - if both the 'file' and 'text' arguments are provided but file = NULL, e.g. markdownToHTML(file = NULL, text = ?), markdownToHTML() can throw an error indicating the file is invalid (thanks, Tyler Rinker, hadley/staticdocs#66)
188 |
189 | - markdownToHTML(text = ?, output = ?) was broken (#54)
190 |
191 | # CHANGES IN markdown VERSION 0.6.5
192 |
193 | ## NEW FEATURES
194 |
195 | - added an argument 'encoding' to markdownToHTML() to specify the character encoding of the input markdown file, and the HTML output file is always encoded in UTF-8 now (thanks, Kohske Takahashi, #50)
196 |
197 | # CHANGES IN markdown VERSION 0.6.4
198 |
199 | ## NEW FEATURES
200 |
201 | - added 'mathjax_embed' to HTML options for embedding the MathJax JavaScript in the HTML document rather than linking to it online. Note the JavaScript code is read from the http instead of https MathJax URL. Contributed by Henrik Bengtsson.
202 |
203 | - added another vignette to show the HTML output of the original vignette (see browseVignettes('markdown'))
204 |
205 | - the default CSS style was tweaked (major changes include: page width is at most 800px, more line height, slightly larger fonts, and a different syntax highlighting theme)
206 |
207 | # CHANGES IN markdown VERSION 0.6.3
208 |
209 | ## NEW FEATURES
210 |
211 | - added a new argument 'template' to markdownToHTML() so that we can customize the HTML template (by default, it uses the template 'resources/markdown.html' in this package); thanks, Nacho Caballero
212 |
213 | - the options markdown.HTML.stylesheet and markdown.HTML.header used in markdownToHTML() can be character vectors (they will be processed by paste(x, collapse = '\n')
214 |
215 | ## MAJOR CHANGES
216 |
217 | - the 'text' argument in markdownToHTML() and renderMarkdown() is treated as lines of input now, i.e. if 'text' is provided, it is passed to the markdown renderer as paste(text, collapse = '\n'); in the previous versions, it was processed by paste(text, collapse = '')
218 |
219 | # CHANGES IN markdown VERSION 0.6
220 |
221 | ## DOCUMENTATION
222 |
223 | - added a package vignette; see browseVignettes(package = 'markdown')
224 |
225 | # CHANGES IN markdown VERSION 0.5.5
226 |
227 | ## NEW FEATURES
228 |
229 | - added a new argument 'header' to markdownToHTML() to insert code into the HTML header (e.g. custom CSS styles)
230 |
231 | ## BUG FIXES
232 |
233 | - fixed #25 and #27: minor documentation problems
234 |
235 | - fixed #26: the HTML output file will be written relative to the current working directory now when it contains images that need to be base64 encoded
236 |
237 | - fixed #28: the image URL should be decoded before the image is based64 encoded
238 |
239 | ## MISC
240 |
241 | - Yihui Xie has taken over the maintainership for this package from Jeffrey Horner
242 |
243 | # CHANGES IN markdown VERSION 0.5.4
244 |
245 | ## NEW FEATURES
246 |
247 | - Both Pandoc title blocks and Jekyll front matter sections are skipped when rendering markdown documents.
248 |
249 | # CHANGES IN markdown VERSION 0.5.3
250 |
251 | ## NEW FEATURES
252 |
253 | - C/C++ is now a supported language for code block highlighting.
254 |
255 | ## MAJOR CHANGES
256 |
257 | - 'hard_wrap' has been dropped while 'mathjax' and 'highlight_code' have been added to the default list of html options.
258 |
259 | ## BUG FIXES
260 |
261 | - fixed parsing of math equations when located at the end of a line.
262 |
263 | # CHANGES IN markdown VERSION 0.5.2
264 |
265 | ## NEW FEATURES
266 |
267 | - with the new 'latex_math' markdown extensions, users can include math equations using several syntaxes. For block level equations, use $$latex ... $$, $$ ... $$, or \[ ... \]. For inline equations, use $latex...$, $...$, or \( ... \).
268 |
269 | ## MAJOR CHANGES
270 |
271 | - the markdown extension 'ignore_math' was replaced with 'latex_math'.
272 |
273 | - users can now use the markdown.HTML.stylesheet option to override the package default stylesheet.
274 |
275 | - setting the fragment_only rendering option or the fragment.only parameter to markdownToHTML will base64 encode images if applicable. version 0.5.1 did not.
276 |
277 | # CHANGES IN markdown VERSION 0.5.1
278 |
279 | ## BUG FIXES
280 |
281 | - fixed a GUIDgenerator bug; for escaping math equations before markdown parsing begins.
282 |
283 | - image encoding was fixed for the case when there are more than one included in a markdown document.
284 |
285 | # CHANGES IN markdown VERSION 0.5
286 |
287 | ## NEW FEATURES
288 |
289 | - added fragment.only parameter to markdownToHTML
290 |
291 | - added new html rendering options base64_images, fragment_only, mathjax, and highlight_code
292 |
293 | - added new markdown extension ignore_math
294 |
295 | ## MAJOR CHANGES
296 |
297 | - removed safelink from default html rendering options
298 |
299 | - the default html rendering options are now hard_wrap, use_xhtml, smartypants, and base64_images.
300 |
301 | ## BUG FIXES
302 |
303 | - fixed syntax errors in C exports
304 |
305 | # CHANGES IN markdown VERSION 0.4
306 |
307 | ## NEW FEATURES
308 |
309 | - added support for post-processing HTML using smartypants filter
310 |
311 | - added optional support for rendering a table of contents
312 |
313 | ## MAJOR CHANGES
314 |
315 | - changed exported C functions to use an rmd_ prefix (eliminating potential symbol conflicts with other packages)
316 |
317 | - changed default html rendering options to use_xhtml, hard_wrap, safelink, and smartypants
318 |
319 | ## BUG FIXES
320 |
321 | - eliminated name collision with render_markdown function in knitr
322 |
323 |
--------------------------------------------------------------------------------
/R/rpubs.R:
--------------------------------------------------------------------------------
1 | #' Upload an HTML file to RPubs
2 | #'
3 | #' This function uploads an HTML file to rpubs.com. If the upload succeeds a
4 | #' list that includes an `id` and `continueUrl` is returned. A browser should be
5 | #' opened to the `continueUrl` to complete publishing of the document. If an
6 | #' error occurs then a diagnostic message is returned in the `error` element of
7 | #' the list.
8 | #' @param title The title of the document.
9 | #' @param htmlFile The path to the HTML file to upload.
10 | #' @param id If this upload is an update of an existing document then the id
11 | #' parameter should specify the document id to update. Note that the id is
12 | #' provided as an element of the list returned by successful calls to
13 | #' `rpubsUpload`.
14 | #' @param properties A named list containing additional document properties
15 | #' (RPubs doesn't currently expect any additional properties, this parameter
16 | #' is reserved for future use).
17 | #' @param method Method to be used for uploading. "internal" uses a plain http
18 | #' socket connection; "curl" uses the curl binary to do an https upload;
19 | #' "rcurl" uses the RCurl package to do an https upload; and "auto" uses the
20 | #' best available method searched for in the following order: "curl", "rcurl",
21 | #' and then "internal". The global default behavior can be configured by
22 | #' setting the `rpubs.upload.method` option (the default is "auto").
23 | #' @return A named list. If the upload was successful then the list contains a
24 | #' `id` element that can be used to subsequently update the document as well
25 | #' as a `continueUrl` element that provides a URL that a browser should be
26 | #' opened to in order to complete publishing of the document. If the upload
27 | #' fails then the list contains an `error` element which contains an
28 | #' explanation of the error that occurred.
29 | #' @export
30 | #' @examples
31 | #' \dontrun{
32 | #' # upload a document
33 | #' result <- rpubsUpload("My document title", "Document.html")
34 | #' if (!is.null(result$continueUrl))
35 | #' browseURL(result$continueUrl) else stop(result$error)
36 | #'
37 | #' # update the same document with a new title
38 | #' updateResult <- rpubsUpload("My updated title", "Document.html", result$id)
39 | #' }
40 | rpubsUpload <- function(title,
41 | htmlFile,
42 | id = NULL,
43 | properties = list(),
44 | method = getOption("rpubs.upload.method", "auto")) {
45 |
46 | # validate inputs
47 | if (!is.character(title))
48 | stop("title must be specified")
49 | if (nzchar(title) == FALSE)
50 | stop("title must be a non-empty string")
51 | if (!is.character(htmlFile))
52 | stop("htmlFile parameter must be specified")
53 | if (!file.exists(htmlFile))
54 | stop("specified htmlFile does not exist")
55 | if (!is.list(properties))
56 | stop("properties parameter must be a named list")
57 |
58 | parseHeader <- function(header) {
59 | split <- strsplit(header, ": ")[[1]]
60 | if (length(split) == 2)
61 | list(name = split[1], value = split[2])
62 | }
63 |
64 | jsonEscapeString <- function(value) {
65 | chars <- strsplit(value, "")[[1]]
66 | chars <- vapply(chars, function(x) {
67 | if (x %in% c('"', '\\', '/'))
68 | paste('\\', x, sep='')
69 | else if (charToRaw(x) < 20)
70 | paste('\\u', toupper(format(as.hexmode(as.integer(charToRaw(x))),
71 | width=4)),
72 | sep='')
73 | else x
74 | }, character(1))
75 | paste(chars, sep="", collapse="")
76 | }
77 |
78 | jsonProperty <- function(name, value) {
79 | paste("\"",
80 | jsonEscapeString(enc2utf8(name)),
81 | "\" : \"",
82 | jsonEscapeString(enc2utf8(value)),
83 | "\"",
84 | sep="")
85 | }
86 |
87 | regexExtract <- function(re, input) {
88 | match <- regexec(re, input)
89 | matchLoc <- match[1][[1]]
90 | if (length(matchLoc) > 1) {
91 | matchLen <-attributes(matchLoc)$match.length
92 | url <- substr(input, matchLoc[2], matchLoc[2] + matchLen[2]-1)
93 | url
94 | }
95 | }
96 |
97 | # NOTE: we parse the json naively using a regex because:
98 | # - We don't want to take a dependency on a json library for just this case
99 | # - We know the payload is an ascii url so we don't need a robust parser
100 | parseContinueUrl <- function(continueUrl) {
101 | regexExtract("\\{\\s*\"continueUrl\"\\s*:\\s*\"([^\"]+)\"\\s*\\}",
102 | continueUrl)
103 | }
104 |
105 | parseHttpStatusCode <- function(statusLine) {
106 | statusCode <- regexExtract("HTTP/[0-9]+\\.[0-9]+ ([0-9]+).*", statusLine)
107 | if (is.null(statusCode)) -1 else as.integer(statusCode)
108 | }
109 |
110 | pathFromId <- function(id) {
111 | split <- strsplit(id, "^https?://[^/]+")[[1]]
112 | if (length(split) == 2) split[2]
113 | }
114 |
115 | buildPackage <- function(title,
116 | htmlFile,
117 | properties = list()) {
118 |
119 | # build package.json
120 | packageJson <- "{"
121 | packageJson <- paste(packageJson, jsonProperty("title", title), ",")
122 | for (name in names(properties)) {
123 | if (nzchar(name) == FALSE)
124 | stop("all properties must be named")
125 | value <- properties[[name]]
126 | packageJson <- paste(packageJson, jsonProperty(name, value), ",")
127 | }
128 | packageJson <- substr(packageJson, 1, nchar(packageJson)-1)
129 | packageJson <- paste(packageJson,"}")
130 |
131 | # create a tempdir to build the package in and copy the files to it
132 | fileSep <- .Platform$file.sep
133 | packageDir <- tempfile()
134 | dir.create(packageDir)
135 | packageFile <- function(fileName) {
136 | paste(packageDir,fileName,sep=fileSep)
137 | }
138 | writeLines(packageJson, packageFile("package.json"))
139 | file.copy(htmlFile, packageFile("index.html"))
140 |
141 | # switch to the package dir for building
142 | oldWd <- getwd()
143 | setwd(packageDir)
144 | on.exit(setwd(oldWd))
145 |
146 | # create the tarball
147 | tarfile <- tempfile("package", fileext = ".tar.gz")
148 | utils::tar(tarfile, files = ".", compression = "gzip")
149 |
150 | # return the full path to the tarball
151 | return (tarfile)
152 | }
153 |
154 | # Use skipDecoding=TRUE if transfer-encoding: chunked but the
155 | # chunk decoding has already been performed on conn
156 | readResponse <- function(conn, skipDecoding) {
157 | # read status code
158 | resp <- readLines(conn, 1)
159 | statusCode <- parseHttpStatusCode(resp[1])
160 |
161 | # read response headers
162 | contentLength <- NULL
163 | location <- NULL
164 | transferEncoding <- NULL
165 | repeat {
166 | resp <- readLines(conn, 1)
167 | if (nzchar(resp) == 0)
168 | break
169 |
170 | header <- parseHeader(resp)
171 | # Case insensitive header name comparison
172 | headerName <- tolower(header$name)
173 | if (!is.null(header)) {
174 | if (identical(headerName, "content-type"))
175 | contentType <- header$value
176 | if (identical(headerName, "content-length"))
177 | contentLength <- as.integer(header$value)
178 | if (identical(headerName, "location"))
179 | location <- header$value
180 | if (identical(headerName, "transfer-encoding"))
181 | transferEncoding <- tolower(header$value)
182 | }
183 | }
184 |
185 | # read the response content
186 | content <- if (is.null(transferEncoding) || skipDecoding) {
187 | if (!is.null(contentLength)) {
188 | rawToChar(readBin(conn, what = 'raw', n=contentLength))
189 | }
190 | else {
191 | paste(readLines(conn, warn = FALSE), collapse = "\r\n")
192 | }
193 | } else if (identical(transferEncoding, "chunked")) {
194 | accum <- ""
195 | repeat {
196 | resp <- readLines(conn, 1)
197 | resp <- sub(";.*", "", resp) # Ignore chunk extensions
198 | chunkLen <- as.integer(paste("0x", resp, sep = ""))
199 | if (is.na(chunkLen)) {
200 | stop("Unexpected chunk length")
201 | }
202 | if (identical(chunkLen, 0L)) {
203 | break
204 | }
205 | accum <- paste0(accum, rawToChar(readBin(conn, what = 'raw', n=chunkLen)))
206 | # Eat CRLF
207 | if (!identical("\r\n", rawToChar(readBin(conn, what = 'raw', n=2)))) {
208 | stop("Invalid chunk encoding: missing CRLF")
209 | }
210 | }
211 | accum
212 | } else {
213 | stop("Unexpected transfer encoding")
214 | }
215 |
216 | # return list
217 | list(status = statusCode,
218 | location = location,
219 | contentType = contentType,
220 | content = content)
221 | }
222 |
223 | # internal sockets implementation of upload (supports http-only)
224 | internalUpload <- function(path,
225 | contentType,
226 | headers,
227 | packageFile) {
228 |
229 | # read file in binary mode
230 | fileLength <- file.info(packageFile)$size
231 | fileContents <- readBin(packageFile, what="raw", n=fileLength)
232 |
233 | # build http request
234 | request <- NULL
235 | request <- c(request, paste("POST ", path, " HTTP/1.1\r\n", sep=""))
236 | request <- c(request, "User-Agent: RStudio\r\n")
237 | request <- c(request, "Host: api.rpubs.com\r\n")
238 | request <- c(request, "Accept: */*\r\n")
239 | request <- c(request, paste("Content-Type: ", contentType, "\r\n", sep=""))
240 | request <- c(request, paste("Content-Length: ", fileLength, "\r\n", sep=""))
241 | for (name in names(headers)) {
242 | request <- c(request,
243 | paste(name, ": ", headers[[name]], "\r\n", sep=""))
244 | }
245 | request <- c(request, "\r\n")
246 |
247 | # open socket connection
248 | conn <- socketConnection(host="api.rpubs.com",
249 | port=80,
250 | open="w+b",
251 | blocking=TRUE)
252 | on.exit(close(conn))
253 |
254 | # write the request header and file payload
255 | writeBin(charToRaw(paste(request,collapse="")), conn, size=1)
256 | writeBin(fileContents, conn, size=1)
257 |
258 | # read the response
259 | readResponse(conn, skipDecoding = FALSE)
260 | }
261 |
262 |
263 | rcurlUpload <- function(path,
264 | contentType,
265 | headers,
266 | packageFile) {
267 |
268 | # url to post to
269 | url <- paste("https://api.rpubs.com", path, sep = "")
270 |
271 | # upload package file
272 | params <- list(file = RCurl::fileUpload(filename = packageFile,
273 | contentType = contentType))
274 |
275 | # use custom header and text gatherers
276 | sslpath <- system.file("CurlSSL", "cacert.pem", package = "RCurl")
277 | options <- RCurl::curlOptions(url, cainfo = sslpath)
278 | headerGatherer <- RCurl::basicHeaderGatherer()
279 | options$headerfunction <- headerGatherer$update
280 | textGatherer <- RCurl::basicTextGatherer()
281 | options$writefunction <- textGatherer$update
282 |
283 | # add extra headers
284 | extraHeaders <- as.character(headers)
285 | names(extraHeaders) <- names(headers)
286 | options$httpheader <- extraHeaders
287 |
288 | # post the form
289 | RCurl::postForm(paste("https://api.rpubs.com", path, sep=""),
290 | .params = params,
291 | .opts = options,
292 | useragent = "RStudio")
293 |
294 | # return list
295 | headers <- headerGatherer$value()
296 | location <- if ("Location" %in% names(headers)) headers[["Location"]]
297 | list(status = as.integer(headers[["status"]]),
298 | location = location,
299 | contentType <- headers[["Content-Type"]],
300 | content = textGatherer$value())
301 |
302 | }
303 |
304 | curlUpload <- function(path,
305 | contentType,
306 | headers,
307 | packageFile) {
308 |
309 | fileLength <- file.info(packageFile)$size
310 |
311 | extraHeaders <- character()
312 | for (header in names(headers)) {
313 | extraHeaders <- paste(extraHeaders, "--header")
314 | extraHeaders <- paste(extraHeaders,
315 | paste(header,":",headers[[header]], sep=""))
316 | }
317 |
318 | outputFile <- tempfile()
319 |
320 | command <- paste("curl",
321 | "-X",
322 | "POST",
323 | "--data-binary",
324 | shQuote(paste("@", packageFile, sep="")),
325 | "-i",
326 | "--header", paste("Content-Type:",contentType, sep=""),
327 | "--header", paste("Content-Length:", fileLength, sep=""),
328 | extraHeaders,
329 | "--header", "Expect:",
330 | "--silent",
331 | "--show-error",
332 | "-o", shQuote(outputFile),
333 | paste("https://api.rpubs.com", path, sep=""))
334 |
335 | result <- system(command)
336 |
337 | if (result == 0) {
338 | fileConn <- file(outputFile, "rb")
339 | on.exit(close(fileConn))
340 | readResponse(fileConn, skipDecoding = TRUE)
341 | } else {
342 | stop(paste("Upload failed (curl error", result, "occurred)"))
343 | }
344 | }
345 |
346 | uploadFunction <- if (is.function(method)) {
347 | method
348 | } else switch(
349 | method,
350 | "auto" = {
351 | if (nzchar(Sys.which("curl"))) curlUpload else {
352 | if (suppressWarnings(requireNamespace("RCurl", quietly = TRUE))) {
353 | rcurlUpload
354 | } else internalUpload
355 | }
356 | },
357 | "internal" = internalUpload,
358 | "curl" = curlUpload,
359 | "rcurl" = rcurlUpload,
360 | stop(paste("Invalid upload method specified:",method))
361 | )
362 |
363 | # build the package
364 | packageFile <- buildPackage(title, htmlFile, properties)
365 |
366 | # determine whether this is a new doc or an update
367 | isUpdate <- FALSE
368 | path <- "/api/v1/document"
369 | headers <- list()
370 | headers$Connection <- "close"
371 | if (!is.null(id)) {
372 | isUpdate <- TRUE
373 | path <- pathFromId(id)
374 | headers$`X-HTTP-Method-Override` <- "PUT"
375 | }
376 |
377 |
378 | # send the request
379 | result <- uploadFunction(path,
380 | "application/x-compressed",
381 | headers,
382 | packageFile)
383 |
384 | # check for success
385 | succeeded <- (isUpdate && (result$status == 200)) || (result$status == 201)
386 |
387 | # mark content as UTF-8
388 | content <- result$content
389 | Encoding(content) <- "UTF-8"
390 |
391 | # return either id & continueUrl or error
392 | if (succeeded) {
393 | list(id = ifelse(isUpdate, id, result$location),
394 | continueUrl = parseContinueUrl(content))
395 | } else list(error = content)
396 | }
397 |
--------------------------------------------------------------------------------