为Roxygen @examples部分自动生成控制台输出

4

背景

我正在记录一个R包,希望@examples部分包括(已注释的)输出,以便用户无需运行示例(在控制台等处)即可看到输出结果。

因此,我手工输入了输出内容(#> ...)...

#' @name my_topic
#' 
#' @title My Topic
#' @description A page for the general topic, under which the specific function(s) will appear.
#' @export
#' 
#' @examples
#'   # Demo variables available throughout the topic.
#'   unnamed_vec <- 1:3
#'   unnamed_vec
#'   #> [1] 1 2 3
#'   
#'   named_vec <- c(a = 1, b = 2, c = 3)
#'   named_vec
#'   #> a b c
#'   #> 1 2 3



#' @rdname my_topic
#' @export
#' 
#' @param x An \R object.
#' 
#' @details `my_fun()` is an alias for [base::names()].
#' 
#' @return For `my_fun()`, a `character` vector with the names, or `NULL` if no names are found.
#' 
#' @examples
#'   
#'   
#'   
#'   # An application of `my_fun()` specifically.
#'   my_fun(unnamed_vec)
#'   #> NULL
#'   
#'   my_fun(named_vec)
#'   #> [1] "a" "b" "c"
my_fun <- function(x) {
  base::names(x)
}

...为了在手册中得到以下页面:

My Topic

Description

A page for the general topic, under which the specific function(s) will appear.

Usage

my_fun(x)

Arguments

x An R object.

Details

my_fun() is an alias for base::names().

Value

For my_fun(), a character vector with the names, or NULL if no names are found.

Examples
# Demo variables available throughout the topic.
unnamed_vec <- 1:3
unnamed_vec
#> [1] 1 2 3

named_vec <- c(a = 1, b = 2, c = 3)
named_vec
#> a b c
#> 1 2 3



# An application of `my_fun()` specifically.
my_fun(unnamed_vec)
#> NULL

my_fun(named_vec)
#> [1] "a" "b" "c"

目标

我希望不再手动键入此输出 (#> ...),而是希望能够从可执行代码自动生成它,这样对命令的更改 (my_fun(...)) 就能动态地反映在输出中。

我希望通过 R Markdown 外包这个任务,就像 roxygen2 建议的 代码块 一样:

Code chunks

You can insert executable code with ```{r}, just like in knitr documents. For example:

#' @title Title
#' @details Details
#' ```{r lorem}
#' 1+1
#' ```
#' @md
foo <- function() NULL

注意

我看到网站上的purrr包似乎会自动生成其示例的输出。

Examples

# Reducing `+` computes the sum of a vector while reducing `*`
# computes the product:
1:3 |> reduce(`+`)
#> [1] 6
1:10 |> reduce(`*`)
#> [1] 3628800

从可执行代码中的@examples这里

#' @examples
#' # Reducing `+` computes the sum of a vector while reducing `*`
#' # computes the product:
#' 1:3 |> reduce(`+`)
#' 1:10 |> reduce(`*`)

然而,这个输出在RStudio相应的帮助页面上并没有出现:不过

Examples
# Reducing `+` computes the sum of a vector while reducing `*`
# computes the product:
1:3 |> reduce(`+`)
1:10 |> reduce(`*`)

问题

很不幸,当我尝试在@examples下实现代码块时...

#' @examples
#' ```{r}
#' # Demo variables available throughout the topic.
#' unnamed_vec <- 1:3
#' unnamed_vec
#' 
#' named_vec <- c(a = 1, b = 2, c = 3)
#' named_vec
#' ```

#' @examples
#' ```{r}
#' 
#' 
#' # An application of `my_fun()` specifically.
#' my_fun(unnamed_vec)
#' 
#' my_fun(named_vec)
#' ```

...我对这个手册的意图没有得到预期的结果(如下)。

Examples
# Demo variables available throughout the topic.
unnamed_vec <- 1:3
unnamed_vec
#> [1] 1 2 3

named_vec <- c(a = 1, b = 2, c = 3)
named_vec
#> a b c
#> 1 2 3



# An application of `my_fun()` specifically.
my_fun(unnamed_vec)
#> NULL

my_fun(named_vec)
#> [1] "a" "b" "c"

相反,我的check()触发了以下错误

── R CMD check results ─────────────────────────────────────────────────────────────── pkg 0.0.0.9000 ────
Duration: 18.5s

❯ checking examples ... ERROR
  Running examples in ‘my_topic-Ex.R’ failed
  The error most likely occurred in:
  
  > base::assign(".ptime", proc.time(), pos = "CheckExEnv")
  > ### Name: my_topic
  > ### Title: My Topic
  > ### Aliases: my_topic my_fun
  > 
  > ### ** Examples
  > 
  >   ```{r}
  Error: attempt to use zero-length variable name
  Execution halted

❯ checking for unstated dependencies in examples ... WARNING
  Warning: parse error in file 'lines':
  attempt to use zero-length variable name

1 error ✖ | 1 warning ✖ | 0 notes ✔

注意

我尝试使用@md标签强制使用Markdown...

#' @md
#' @examples

...但错误仍然存在。

不幸的是,我怀疑在@examples标签下的所有内容都会自动被视为纯R代码并执行, 而从不作为R markdown。因此结果不是下面的.Rd...

 \examples{
 Examples
 
 if{html}{\out{<div class="sourceCode r">}}\preformatted{
 # Demo variables available throughout the topic.
 unnamed_vec <- 1:3
 unnamed_vec
 #> [1] 1 2 3
 
 named_vec <- c(a = 1, b = 2, c = 3)
 named_vec
 #> a b c
 #> 1 2 3
 
 
 
 # An application of `my_fun()` specifically.
 my_fun(unnamed_vec)
 #> NULL
 
 my_fun(named_vec)
 #> [1] "a" "b" "c"
 }\if{html}{\out{</div>}}
 }

...但是它不是尝试在R 之外执行此代码,而是在R 内部执行...

 ```{r}
 # Demo variables available throughout the topic.
 unnamed_vec <- 1:3
 unnamed_vec
 
 named_vec <- c(a = 1, b = 2, c = 3)
 named_vec
 ```
 ```{r}
 
 
 
 # An application of `my_fun()` specifically.
 my_fun(unnamed_vec)
 
 my_fun(named_vec)
 ```

在渲染(类似于)下面的.Rd之前:

 \examples{
 Examples
 
 \preformatted{
 ```{r}
 # Demo variables available throughout the topic.
 unnamed_vec <- 1:3
 unnamed_vec
 
 named_vec <- c(a = 1, b = 2, c = 3)
 named_vec
 ```
 ```{r}
 
 
 
 # An application of `my_fun()` specifically.
 my_fun(unnamed_vec)
 
 my_fun(named_vec)
 ```
 }
 }

当然,尝试在R中执行第一行...

 ```{r}

会产生错误,因为符号```代表“零长度变量名称”:

  > ### ** Examples
  > 
  >   ```{r}
  Error: attempt to use zero-length variable name
  Execution halted

问题

有什么规范的方法可以自动生成可执行代码中@examples的控制台输出吗?

我想要避免像下面那样拼凑一个假的例子部分:

#' @md
#' @section Examples:
#' ```{r}
#' # Demo variables available throughout the topic.
#' unnamed_vec <- 1:3
#' unnamed_vec
#' 
#' named_vec <- c(a = 1, b = 2, c = 3)
#' named_vec
#' ```

pkgdown::build_reference 可能是一个不错的起点,但我并不确定是否有一种方法将输出注入到相应的Rd文件中。你可能需要自己处理文本处理的这一部分。 - Mikael Jagan
1个回答

1

你可以使用 tools 中的 parse_RdRd2ex 自己编写。这里我写了一个简单的版本,其中 rdfilecon 是指向 Rd 文件(输入和输出)的路径或文本模式连接,而 comment 则是 \examples{} 内所有输出行的前缀。

值得注意的是,我没有试图将消息或警告与示例输出一起输出,但可以在评估示例代码之前建立合适的调用处理程序来实现。

withExOutput <- function(rdfile, con = stdout(), comment = "## ") {
    ## Parse Rd file
    rd <- tools::parse_Rd(rdfile)

    ## Write example code to *.R
    exfile <- tempfile()
    on.exit(unlink(exfile))
    tools::Rd2ex(rd, out = exfile)

    ## Determine number of lines in 'Rd2ex' header
    exfile.con <- file(exfile, open = "r")
    skip <- 1L
    while (length(zzz <- readLines(exfile.con, n = 1L)) &&
           !length(grep("^### \\*\\*", zzz)))
        skip <- skip + 1L
    while (length(zzz <- readLines(exfile.con, n = 1L)) &&
           !nzchar(zzz))
        skip <- skip + 1L
    close(exfile.con)

    ## Run examples without header and capture stdout/stderr
    x <- capture.output(source(
        exfile, local = new.env(parent = .GlobalEnv),
        echo = TRUE, spaced = FALSE, verbose = FALSE,
        prompt.echo = "~~~~> ", continue.echo = "~~~~+ ",
        skip.echo = skip))

    ## Strip source prompts, comment output lines
    prompted <- grep("^~~~~[>+] ", x)
    x[ prompted] <- sub("^~~~~[>+] ", "", x[prompted])
    x[-prompted] <- paste0(comment, x[-prompted])

    ## Replace \dontrun{}, \dontshow{}, \donttest{}
    s1 <- c("## Not run: ",    "## End(Not run)",
            "## Don't show: ", "## End(Don't show)",
            "## No test: ",    "## End(No test)")
    s2 <- c("\\dontrun{",      "}",
            "\\dontshow{",     "}",
            "\\donttest{",     "}")
    mx <- match(x, s1, 0L)
    x[mx > 0L] <- s2[mx]

    ## Uncomment lines inside of \dontrun{}
    x <- sub("^##D ", "", x)

    ## Delete trailing empty lines
    n <- length(x)
    while (n && !nzchar(x[n]))
        n <- n - 1L
    x <- x[seq_len(n)]

    ## Replace old \examples{} with new {including output}
    rdfile <- tempfile()
    on.exit(unlink(rdfile), add = TRUE)
    writeLines(c("\\examples{", x, "}"), rdfile)
    tags <- vapply(rd, attr, "", "Rd_tag")
    rd[tags == "\\examples"] <- tools::parse_Rd(rdfile)[1L]

    cat(as.character(rd), file = con, sep = "")
}

以任何Rd文件为例:
%% this is add.Rd
\name{add}
\alias{add}
\title{Add two numbers}
\description{Computes \code{a + b} given \code{a} and \code{b}.}
\usage{
add(a, b)
}
\arguments{
  \item{a}{A number.}
  \item{b}{Another number.}
}
\examples{
(a <- 0)
add(a, 1)
\dontrun{
add(a, 2)
}
\dontshow{
add(a, 3)
}
\donttest{
add(a, 4)
}
}

您可以通过使用library来连接您的包,然后使用适当的参数调用withExOutput(例如,确保comment#开头):
add <- function(a, b) a + b # simulating a 'library' call
withExOutput("add.Rd", comment = "#> ") # in the style of 'reprex'

当您接受默认的con = stdout()时,带有示例输出的“新”Rd文件将仅在控制台中打印(假设您没有将其重定向到其他地方):

%% this is add.Rd
\name{add}
\alias{add}
\title{Add two numbers}
\description{Computes \code{a + b} given \code{a} and \code{b}.}
\usage{
add(a, b)
}
\arguments{
  \item{a}{A number.}
  \item{b}{Another number.}
}
\examples{
(a <- 0)
#> [1] 0
add(a, 1)
#> [1] 1
\dontrun{
add(a, 2)
}
\dontshow{
add(a, 3)
#> [1] 3
}
\donttest{
add(a, 4)
#> [1] 4
}
}

嗨,Mikael,感谢您提供的定制解决方案!我一直太忙了没有时间测试,所以还没有将其标记为正确答案。但是您是唯一的回答者,我不想让您的辛勤工作白费,所以我现在已经授予了赏金。如果有进一步使用我的包时的更新,我会在此发表评论。 :) - Greg

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接