Skip to content

Commit

Permalink
close #54: add support for running examples in pkg_manual()
Browse files Browse the repository at this point in the history
  • Loading branch information
yihui committed Dec 12, 2024
1 parent 7ff8d28 commit 9327393
Show file tree
Hide file tree
Showing 9 changed files with 87 additions and 26 deletions.
2 changes: 1 addition & 1 deletion DESCRIPTION
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
Package: litedown
Type: Package
Title: A Lightweight Version of R Markdown
Version: 0.4.9
Version: 0.4.10
Authors@R: c(
person("Yihui", "Xie", role = c("aut", "cre"), email = "[email protected]", comment = c(ORCID = "0000-0003-0645-5666", URL = "https://yihui.org")),
person()
Expand Down
2 changes: 2 additions & 0 deletions NEWS.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@

- Provided templates and a Github action `yihui/litedown/site` to build package websites. See https://yihui.org/litedown/#sec:pkg-site for details.

- Added an argument `examples` to `pkg_manual()` to run examples and show their output (thanks, @TimTaylor, #54).

- Fixed a bug that cross-references to other chapters of a book could not be resolved when previewing a single chapter.

- Fixed a bug that the file navigation by line numbers on code blocks stopped working in `litedown::roam()` due to yihui/lite.js@5e06d19.
Expand Down
2 changes: 1 addition & 1 deletion R/fuse.R
Original file line number Diff line number Diff line change
Expand Up @@ -329,7 +329,7 @@ run_range = function(x, extend = NULL) {
i1 = i1[c(TRUE, k)]; i2 = i2[c(k, TRUE)]
}
}
rbind(i1, i2)
matrix(as.integer(c(i1, i2)), nrow = 2, byrow = TRUE)
}

# convert knitr's inline `r code` to litedown's `{r} code`
Expand Down
5 changes: 3 additions & 2 deletions R/mark.R
Original file line number Diff line number Diff line change
Expand Up @@ -288,8 +288,9 @@ mark = function(input, output = NULL, text = NULL, options = NULL, meta = list()
# build table of contents
ret = add_toc(ret, options)
# add js/css for math
if (!has_math) has_math = length(ret) &&
grepl('$$</p>', ret, fixed = TRUE) # math may be from pkg_manual()'s HTML
if (!has_math) has_math = length(ret) && (
grepl('$$</p>', ret, fixed = TRUE) || grepl('\\)</span>', ret, fixed = TRUE)
) # math may be from pkg_manual()'s HTML
is_katex = TRUE
if (has_math && length(js_math <- js_options(options[['js_math']], 'katex'))) {
is_katex = js_math$package == 'katex'
Expand Down
67 changes: 62 additions & 5 deletions R/package.R
Original file line number Diff line number Diff line change
Expand Up @@ -115,10 +115,11 @@ vig_filter = function(ifile, encoding) {
#' `pkg_desc()` returns an HTML table containing the package metadata.
#' @export
#' @examples
#' \dontrun{
#' litedown::pkg_desc()
#' litedown::pkg_news()
#' litedown::pkg_citation()
#' litedown::pkg_manual()
#' }
pkg_desc = function(name = detect_pkg()) {
fields = c(
'Title', 'Version', 'Description', 'Depends', 'Imports', 'Suggests',
Expand Down Expand Up @@ -271,11 +272,15 @@ tweak_citation = function(x) {

#' @param overview Whether to include the package overview page, i.e., the
#' `{name}-package.Rd` page.
#' @param examples A list of arguments to be passed to [xfun::record()] to run
#' examples each help page, e.g., `list(dev = 'svg', dev.args = list(height =
#' 6))`. If not a list (e.g., `FALSE`), examples will not be run.
#' @return `pkg_manual()` returns all manual pages of the package in HTML.
#' @rdname pkg_desc
#' @export
pkg_manual = function(
name = detect_pkg(), toc = TRUE, number_sections = TRUE, overview = TRUE
name = detect_pkg(), toc = TRUE, number_sections = TRUE, overview = TRUE,
examples = list()
) {
links = tools::findHTMLlinks('')
# resolve internal links (will assign IDs of the form sec:man-ID to all h2)
Expand Down Expand Up @@ -306,10 +311,17 @@ pkg_manual = function(
stop(e)
}, finally = close(con))
# extract body, which may end at </main> (R 4.4.x) or </div></body> (R 4.3.x)
txt = gsub('.*?(<h2[ |>].*)(</main>|</div>\\s*</body>).*', '\\1', one_string(txt))
txt = gsub('(?s).*?(?=<h2)', '', one_string(txt), perl = TRUE)
txt = gsub('(</main>|</div>\\s*</body>).*', '', txt)
# free math from <code>
txt = gsub(r2, '<p>$$\\1$$</p>', txt)
txt = gsub(r1, '\\\\(\\1\\\\)', txt)
txt = gsub(r1, '<span>\\\\(\\1\\\\)</span>', txt)
# run examples
if (is.list(examples)) {
xfun::pkg_attach(name)
default = list(print = NA, dev.path = 'manual/', dev.args = list(width = 9, height = 7))
txt = run_examples(txt, merge_list(default, examples), sans_ext(i))
}
# remove existing ID and class
for (a in c('id', 'class')) txt = gsub(sprintf('(<h2[^>]*?) %s="[^"]+"', a), '\\1', txt)
if (cl != '') txt = sub('<h2', paste0('<h2', cl), txt, fixed = TRUE)
Expand Down Expand Up @@ -342,8 +354,53 @@ pkg_manual = function(
res = gsub('<code class="language-R"', '<code class="language-r"', res, fixed = TRUE)
res = gsub('&#8288;', '', res, fixed = TRUE)
res = gsub('<table>', '<table class="table-full">', res, fixed = TRUE)
style = gen_tag(jsd_resolve(jsdelivr('css/manual.min.css')))
new_asis(c(style, toc, res))
}

new_asis(c(toc, res))
run_examples = function(html, config, path) {
config$dev.path = path = paste0(config$dev.path, path)
on.exit(xfun::del_empty_dir(dirname(path)), add = TRUE)
r = '(?s).*?<pre><code[^>]*>(?s)(.+?)</code></pre>'
match_replace(html, paste0('(?<=<h3>Examples</h3>)', r), function(x) {
code = gsub(r, '\\1', x, perl = TRUE)
code = restore_html(str_trim(code))
nr1 = 'if (FALSE) { ## Not run'
nr2 = '} ## Not run'
code = gsub('\n?## Not run:\\s*?\n', paste0('\n', nr1, '\n'), code)
code = gsub('\n+## End[(]Not run[)]\n*', paste0('\n', nr2, '\n'), code)
res = do.call(xfun::record, merge_list(config, list(code = code, envir = globalenv())))
idx = seq_along(res); cls = class(res)
for (i in idx) {
ri = res[[i]]; ci = class(ri)
# disable asis output since it may contain raw HTML
if ('record_asis' %in% ci) class(res[[i]]) = 'record_output'
# split the dontrun block
if ('record_source' %in% ci && !any(is.na(nr <- match(c(nr1, nr2), ri)))) {
i1 = nr[1]; i2 = nr[2]
new_block = function(i, ...) {
b = trim_blank(one_string(ri[i]))
if (xfun::is_blank(b)) b = character()
list(structure(b, class = c(ci, ...)))
}
if (i1 > 1) {
res = c(res, new_block((i1 + 1):(i2 - 1), 'fade'))
idx = c(idx, i)
}
n = length(ri)
if (i2 < n) {
res = c(res, new_block((i2 + 1):n))
idx = c(idx, i)
}
res[i] = if (i1 > 1) new_block(1:(i1 - 1)) else
new_block((i1 + 1):(i2 - 1), 'fade')
}
}
res = res[order(idx)]
class(res) = cls
res = one_string(c('', format(res, 'markdown')))
res
})
}

detect_pkg = function(error = TRUE) {
Expand Down
18 changes: 10 additions & 8 deletions inst/resources/default.css
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,11 @@ pre code { display: block; padding: 1em; overflow-x: auto; }
code { font-family: 'DejaVu Sans Mono', 'Droid Sans Mono', 'Lucida Console', Consolas, Monaco, monospace; }
:not(pre) > code, code[class], .box > .caption { background-color: #f8f8f8; }
pre > code:is(:not([class]), .language-plain, .language-none), .box { background-color: inherit; border: 1px solid #eee; }
pre > .message { border-color: #9eeaf9; }
pre > .warning { background: #fff3cd; border-color: #fff3cd; }
pre > .error { background: #f8d7da; border-color: #f8d7da; }
pre > code {
&.message { border-color: #9eeaf9; }
&.warning { background: #fff3cd; border-color: #fff3cd; }
&.error { background: #f8d7da; border-color: #f8d7da; }
}
.fenced-chunk { border-left: 1px solid #666; }
.code-fence {
opacity: .4;
Expand All @@ -36,10 +38,6 @@ table {
th, td { padding: 5px; font-variant-numeric: tabular-nums; }
thead, tfoot, tr:nth-child(even) { background: whitesmoke; }
}
.table-full {
width: 100%;
td { vertical-align: baseline; }
}
blockquote {
color: #666;
margin: 0;
Expand Down Expand Up @@ -81,11 +79,15 @@ section.footnotes {
margin-top: 2em;
&::before { content: ""; display: block; max-width: 20em; }
}
.fade {
background: repeating-linear-gradient(135deg, white, white 30px, #ddd 32px, #ddd 32px);
opacity: 0.6;
}

@media print {
body { max-width: 100%; }
tr, img { page-break-inside: avoid; }
}
@media only screen and (min-width: 992px) {
pre:has(.line-numbers):not(:hover) { white-space: pre; }
body:not(.pagesjs) pre:has(.line-numbers):not(:hover) { white-space: pre; }
}
4 changes: 0 additions & 4 deletions inst/resources/snap.css
Original file line number Diff line number Diff line change
Expand Up @@ -35,10 +35,6 @@ a { color: #eb4a47; }
background-color: #eee;
filter: invert(1);
}
.fade {
background: repeating-linear-gradient(135deg, white, white 30px, #ddd 32px, #ddd 32px);
opacity: 0.6;
}
.center { text-align: center; }
.slide-container h2 .section-number {
display: inline-block;
Expand Down
9 changes: 7 additions & 2 deletions man/pkg_desc.Rd

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 1 addition & 3 deletions site/manual.Rmd
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,4 @@ h2 {
}
```

```{r, echo = FALSE}
litedown::pkg_manual()
```
`{r} litedown::pkg_manual()`

0 comments on commit 9327393

Please sign in to comment.