R 函数 'box::help()' 无法生成帮助文件:"Invalid Argument"
R Function 'box::help()' Cannot Generate Help File: "Invalid Argument"
动机
我和我的同事经常在 R 中创建临时脚本,以对专有数据执行 ETL 并为客户生成自动报告。为了一致性、模块化和可重用性,我正在尝试标准化我们的方法。
特别是,我想将我们最常用的函数合并到一个中央目录中,并像访问专有 R 包中的函数一样访问它们。然而,作为一名 R 开发人员,我还比较生疏,而我的队友在 R 开发方面的经验就更少了。因此,正式包的开发目前是不可行的。
方法
幸运的是,box
package, by Stack Overflow's very own Konrad Rudolph, provides (among other modularity) an accessible approach to approximate the behavior of an R package. Unlike the rigorous development process outlined by the RStudio team, box
requires only that one create a regular .R
file, in a meaningful location, with roxygen2
documentation (#'
) and explicit @export
s:
Writing modules
The module bio/seq
, which we have used in the previous section, is
implemented in the file bio/seq.r
. The file seq.r
is,
by and large, a normal R source file, which happens to live in a
directory named bio
.
In fact, there are only three things worth mentioning:
Documentation. Functions in the module file can be documented using ‘roxygen2’ syntax.
It works the same as for packages. The ‘box’ package parses the
documentation and makes it available via box::help
. Displaying
module help requires that ‘roxygen2’ is installed.
Export declarations. Similar to packages, modules explicitly need to declare which names they export; they do this using the
annotation comment #' @export
in front of the name. Again, this
works similarly to ‘roxygen2’ (but does not require having that
package installed).
⋮
目前,我正在修改一个特定的模块,将其“导入”到脚本中。虽然“导入”本身可以无缝运行,但我似乎无法访问我的函数的文档。
代码
我正在 Lenovo ThinkPad 运行 Windows 10 Enterprise 上试验 box
。我创建了一个名为 Script.R
的脚本,其位置作为我的工作目录。我的模块作为不起眼的文件time.R
存在于相对子目录./Resources/Modules
中,转载于此:
###########################
## Relative Date Windows ##
###########################
#' @title Past Day of Week
#' @description Determine the date of the given weekday that fell a given number
#' of weeks before the given date.
#' @param from \code{Date} object. The point of reference, from which we go
#' backwards. Defaults to current \code{Sys.Date()}.
#' @param back \code{integer}. The number of weeks to go backward from the point
#' of reference; negative values go forward. Defaults to \code{1}, for last
#' week. Weeks begin on \code{"Monday"}.
#' @param weekday \code{character}. The weekday within the week targeted by
#' \code{back}; one of \code{c("Monday", "Tuesday", "Wednesday", "Thursday",
#' "Friday", "Saturday", "Sunday")}.
#' @export
#' @return The date of the \code{weekday} falling in the week \code{back} weeks
#' prior to the week in which \code{from} falls. Defaults to \code{"Monday"}.
past_weekday <- function(from = Sys.Date(),
back = 1,
weekday = c("Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", "Sunday")
) {
cycle <- c("Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", "Sunday")
from <- as.Date(from)
back <- as.integer(back)
weekday_index <- (which(cycle == weekday[1]) - 1) %% 7
from_index <- (which(cycle == "Sunday") + as.POSIXlt(from)$wday - 1) %% 7
weekdate <- as.Date(from) - lubridate::days(from_index) - lubridate::weeks(as.numeric(back)) + lubridate::days(weekday_index)
return(as.Date(weekdate))
}
遵守 roxygen2
文档,如特殊 #'
注释和 @
标签所示。 past_weekday()
的 文档 比函数 本身 .
更让我感兴趣
最后也是最不重要的,这里转载Script.R
本身:
# Set the working directory to the location of this very script.
setwd(this.path::this.dir())
# Access the functions in 'time.R' by relative location.
box::use(Resources/Modules/time)
# Run the function with its default values.
time$past_weekday()
# View the help page for the function.
box::help(time$past_weekday)
理论上,最后一行将通过 box::help()
:
显示 past_weekday()
的文档
box
vignette 给出了一个简单的例子:
We can also display the interactive help for individual names using the box::help
function, e.g.:
box::help(seq$revcomp)
问题
Script.R
的前三行正是我想要的。也就是说,它们将 time
作为环境加载到 R 中,我可以从中通过 time$past_weekday()
访问 past_weekday()
。 module$function()
语法类似于正式包中函数的限定:package::function()
。事实上,past_weekday()
本身就如预期的那样工作:
time$past_weekday()
# [1] "2021-07-19"
但是,当我尝试以交互方式访问文档时
box::help(time$past_weekday)
控制台显示以下警告
Warning messages:
1: In utils::packageDescription(package, fields = "Version") :
no package 'PKG' was found
2: In file.create(to[okay]) :
cannot create file 'C:\Users\greg\AppData\Local\Temp\RtmpYBTTyG/.R/doc/html/module:Resources/Modules/time.html', reason 'Invalid argument'
并且交互式帮助 window 是空的,但是对于此错误消息:
对于我的团队来说,这可能是一个严重的问题。由于我们经常依赖彼此编写的有用功能,因此我们团队中的任何用户都能够轻松访问函数作者的清晰文档……就像用户习惯于使用正式的 R 包一样。如果没有这种能力,用户必须要么打扰作者进行澄清,要么在没有清楚地理解函数的目的和限制的情况下犯错误。
疑点
当我阅读警告时
In file.create(to[okay]) :
cannot create file 'C:\Users\greg\AppData\Local\Temp\RtmpYBTTyG/.R/doc/html/module:Resources/Modules/time.html', reason 'Invalid argument'
我被文件路径吸引了
C:\Users\greg\AppData\Local\Temp\RtmpYBTTyG/.R/doc/html/module:Resources/Modules/time.html
作为 Invalid argument
到 file.create()
的原因。 据我所知,包含冒号 :
的目录名称 .../module:Resources/...
在 Windows and elsewhere.
上是非法的
确实,当我提供另一个非法文件路径 ./illegal:directory:name/missing.txt
到 file.create()
file.create('./illegal:directory:name/missing.txt')
# [1] FALSE
我收到同样的警告:
Warning message:
In file.create("./illegal:directory:name/missing.txt") :
cannot create file './illegal:directory:name/missing.txt', reason 'Invalid argument'
display_help(doc, paste0('module:', mod_name), help_type)
# ^
# Here
但是,这个诊断似乎太简单了。坦率地说,如果在经验丰富的开发人员设计的包中发现这样的可移植性问题,我会感到非常惊讶。我发现我更有可能只是超出了我的理解范围。
我错过了什么?
更新 1
我在我的 MacBook Air 运行 Mojave 上试过了,它确实有效!虽然我仍然在控制台上收到第一条(相当奇怪的)警告消息
Warning message:
In utils::packageDescription(package, fields = "Version") :
no package 'PKG' was found
交互式帮助 window 确实显示 预期文档:
当然,这并不能完全解决我的问题——脚本将在 VM 运行 Windows 上执行,就像我的 Lenovo 和我公司使用的所有其他计算机一样。但是,它确实支持这个问题特定于 box
on Windows.
的假设
更新 2
康拉德好心confirmed that this is indeed a bug, and he's working on a fix。 非常感谢康拉德的澄清和回应!
如前所述,that’s a bug, now fixed。
但既然我们来了,就用法说一句:
# Set the working directory to the location of this very script.
setwd(this.path::this.dir())
一般不推荐这样做。引用 Jenny Bryan:
If the first line of your R script is
setwd("C:\Users\jenny\path\that\only\I\have")
I will come into your office and SET YOUR COMPUTER ON FIRE .
‘box’也不需要这个;相反,想法是配置一个 global module search path(相当于 R 的包库,参见 .libPaths()
),例如通过 box.path
选项(这通常会进入用户的 .Rprofile
配置 - 而不是 在脚本本身中!):
options(box.path = 'C:\User\Konrad\some\path')
之后,box::use
将找到安装在此搜索路径中的模块。
如前所述,这应该是一个全局设置。要使用特定于项目的模块,您不会设置全局搜索路径;相反,你会使用相对导入:
box::use(./Resources/Modules/time)
无论工作目录如何,这都应该有效;它改为使用调用脚本的位置。因此,“box”永远不需要 this.path::this.dir()
或类似的 hack。为了查找数据文件,“box”提供了 box::file
功能,无论当前工作目录如何,该功能都可以使用。
动机
我和我的同事经常在 R 中创建临时脚本,以对专有数据执行 ETL 并为客户生成自动报告。为了一致性、模块化和可重用性,我正在尝试标准化我们的方法。
特别是,我想将我们最常用的函数合并到一个中央目录中,并像访问专有 R 包中的函数一样访问它们。然而,作为一名 R 开发人员,我还比较生疏,而我的队友在 R 开发方面的经验就更少了。因此,正式包的开发目前是不可行的。
方法
幸运的是,box
package, by Stack Overflow's very own Konrad Rudolph, provides (among other modularity) an accessible approach to approximate the behavior of an R package. Unlike the rigorous development process outlined by the RStudio team, box
requires only that one create a regular .R
file, in a meaningful location, with roxygen2
documentation (#'
) and explicit @export
s:
Writing modules
The module
bio/seq
, which we have used in the previous section, is implemented in the filebio/seq.r
. The fileseq.r
is, by and large, a normal R source file, which happens to live in a directory namedbio
.In fact, there are only three things worth mentioning:
Documentation. Functions in the module file can be documented using ‘roxygen2’ syntax. It works the same as for packages. The ‘box’ package parses the
documentation and makes it available viabox::help
. Displaying module help requires that ‘roxygen2’ is installed.Export declarations. Similar to packages, modules explicitly need to declare which names they export; they do this using the annotation comment
#' @export
in front of the name. Again, this works similarly to ‘roxygen2’ (but does not require having that package installed).⋮
目前,我正在修改一个特定的模块,将其“导入”到脚本中。虽然“导入”本身可以无缝运行,但我似乎无法访问我的函数的文档。
代码
我正在 Lenovo ThinkPad 运行 Windows 10 Enterprise 上试验 box
。我创建了一个名为 Script.R
的脚本,其位置作为我的工作目录。我的模块作为不起眼的文件time.R
存在于相对子目录./Resources/Modules
中,转载于此:
###########################
## Relative Date Windows ##
###########################
#' @title Past Day of Week
#' @description Determine the date of the given weekday that fell a given number
#' of weeks before the given date.
#' @param from \code{Date} object. The point of reference, from which we go
#' backwards. Defaults to current \code{Sys.Date()}.
#' @param back \code{integer}. The number of weeks to go backward from the point
#' of reference; negative values go forward. Defaults to \code{1}, for last
#' week. Weeks begin on \code{"Monday"}.
#' @param weekday \code{character}. The weekday within the week targeted by
#' \code{back}; one of \code{c("Monday", "Tuesday", "Wednesday", "Thursday",
#' "Friday", "Saturday", "Sunday")}.
#' @export
#' @return The date of the \code{weekday} falling in the week \code{back} weeks
#' prior to the week in which \code{from} falls. Defaults to \code{"Monday"}.
past_weekday <- function(from = Sys.Date(),
back = 1,
weekday = c("Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", "Sunday")
) {
cycle <- c("Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", "Sunday")
from <- as.Date(from)
back <- as.integer(back)
weekday_index <- (which(cycle == weekday[1]) - 1) %% 7
from_index <- (which(cycle == "Sunday") + as.POSIXlt(from)$wday - 1) %% 7
weekdate <- as.Date(from) - lubridate::days(from_index) - lubridate::weeks(as.numeric(back)) + lubridate::days(weekday_index)
return(as.Date(weekdate))
}
遵守 roxygen2
文档,如特殊 #'
注释和 @
标签所示。 past_weekday()
的 文档 比函数 本身 .
最后也是最不重要的,这里转载Script.R
本身:
# Set the working directory to the location of this very script.
setwd(this.path::this.dir())
# Access the functions in 'time.R' by relative location.
box::use(Resources/Modules/time)
# Run the function with its default values.
time$past_weekday()
# View the help page for the function.
box::help(time$past_weekday)
理论上,最后一行将通过 box::help()
:
past_weekday()
的文档
box
vignette 给出了一个简单的例子:
We can also display the interactive help for individual names using the
box::help
function, e.g.:box::help(seq$revcomp)
问题
Script.R
的前三行正是我想要的。也就是说,它们将 time
作为环境加载到 R 中,我可以从中通过 time$past_weekday()
访问 past_weekday()
。 module$function()
语法类似于正式包中函数的限定:package::function()
。事实上,past_weekday()
本身就如预期的那样工作:
time$past_weekday()
# [1] "2021-07-19"
但是,当我尝试以交互方式访问文档时
box::help(time$past_weekday)
控制台显示以下警告
Warning messages:
1: In utils::packageDescription(package, fields = "Version") :
no package 'PKG' was found
2: In file.create(to[okay]) :
cannot create file 'C:\Users\greg\AppData\Local\Temp\RtmpYBTTyG/.R/doc/html/module:Resources/Modules/time.html', reason 'Invalid argument'
并且交互式帮助 window 是空的,但是对于此错误消息:
对于我的团队来说,这可能是一个严重的问题。由于我们经常依赖彼此编写的有用功能,因此我们团队中的任何用户都能够轻松访问函数作者的清晰文档……就像用户习惯于使用正式的 R 包一样。如果没有这种能力,用户必须要么打扰作者进行澄清,要么在没有清楚地理解函数的目的和限制的情况下犯错误。
疑点
当我阅读警告时
In file.create(to[okay]) :
cannot create file 'C:\Users\greg\AppData\Local\Temp\RtmpYBTTyG/.R/doc/html/module:Resources/Modules/time.html', reason 'Invalid argument'
我被文件路径吸引了
C:\Users\greg\AppData\Local\Temp\RtmpYBTTyG/.R/doc/html/module:Resources/Modules/time.html
作为 Invalid argument
到 file.create()
的原因。 据我所知,包含冒号 :
的目录名称 .../module:Resources/...
在 Windows and elsewhere.
确实,当我提供另一个非法文件路径 ./illegal:directory:name/missing.txt
到 file.create()
file.create('./illegal:directory:name/missing.txt')
# [1] FALSE
我收到同样的警告:
Warning message:
In file.create("./illegal:directory:name/missing.txt") :
cannot create file './illegal:directory:name/missing.txt', reason 'Invalid argument'
display_help(doc, paste0('module:', mod_name), help_type)
# ^
# Here
但是,这个诊断似乎太简单了。坦率地说,如果在经验丰富的开发人员设计的包中发现这样的可移植性问题,我会感到非常惊讶。我发现我更有可能只是超出了我的理解范围。
我错过了什么?
更新 1
我在我的 MacBook Air 运行 Mojave 上试过了,它确实有效!虽然我仍然在控制台上收到第一条(相当奇怪的)警告消息
Warning message:
In utils::packageDescription(package, fields = "Version") :
no package 'PKG' was found
交互式帮助 window 确实显示 预期文档:
当然,这并不能完全解决我的问题——脚本将在 VM 运行 Windows 上执行,就像我的 Lenovo 和我公司使用的所有其他计算机一样。但是,它确实支持这个问题特定于 box
on Windows.
更新 2
康拉德好心confirmed that this is indeed a bug, and he's working on a fix。 非常感谢康拉德的澄清和回应!
如前所述,that’s a bug, now fixed。
但既然我们来了,就用法说一句:
# Set the working directory to the location of this very script. setwd(this.path::this.dir())
一般不推荐这样做。引用 Jenny Bryan:
If the first line of your R script is
setwd("C:\Users\jenny\path\that\only\I\have")
I will come into your office and SET YOUR COMPUTER ON FIRE .
‘box’也不需要这个;相反,想法是配置一个 global module search path(相当于 R 的包库,参见 .libPaths()
),例如通过 box.path
选项(这通常会进入用户的 .Rprofile
配置 - 而不是 在脚本本身中!):
options(box.path = 'C:\User\Konrad\some\path')
之后,box::use
将找到安装在此搜索路径中的模块。
如前所述,这应该是一个全局设置。要使用特定于项目的模块,您不会设置全局搜索路径;相反,你会使用相对导入:
box::use(./Resources/Modules/time)
无论工作目录如何,这都应该有效;它改为使用调用脚本的位置。因此,“box”永远不需要 this.path::this.dir()
或类似的 hack。为了查找数据文件,“box”提供了 box::file
功能,无论当前工作目录如何,该功能都可以使用。