我正在尝试覆盖tidy.source
包的knitr
功能。问题是tidy.source
包中定义了formatR
,knitr
包由get("tidy.source", envir=asNamespace("knitr"))
包导入。
如果我跑:
tidy.source
我得到原始代码。
所以我很想用{/ p>覆盖assignInNamespace ("tidy.source", function()print("My tidy.source"), "knitr")
Error in bindingIsLocked(x, ns) : no binding for "tidy.source"
,
但我明白了:
tidy.source
。
事实上,formatR
在knitr
中定义,并由assignInNamespace ("tidy.source", function()print("My tidy.source"), "formatR")
继承。用:
get("tidy.source", envir=asNamespace("knitr"))
一切都很顺利,但再次检查knitr
表明内部"package:main"
没有任何变化。
有任何帮助吗?
由于 knitr / formatR 的新开发版本,此问题已部分过时。非常感谢Yihui注意到这个讨论,并决定更新他的包。参见:
https://github.com/yihui/formatR/commit/6f70360f359caa8d2bb33190a1c89530defb0e98
我绝对可以从 Sweave 切换到 knitr 。
关于覆盖进口包裹功能的一般性问题仍未解决。由于它不再与 knitr / formatR 包相关,我将以更一般的术语重述它。
假设您有一个包 main 导入包 imp 。如果您加载前者,"main"
显示在附加的包列表中,"sub"
和assign("exp.sub.func", exp.sub.func.mod, asNamespace ("sub"))
显示加载的命名空间的名称。
假设 main 导入导出的 sub 函数 exp.sub.func ,该函数调用未导出的子 function prv.sub.func 。如果您想使用 exp.sub.func.mod 更改/自定义 exp.sub.func ,您可以考虑使用:
sub::exp.sub.func
因此,通过运行Error in [...] : object 'prv.sub.func' not found
,您将获得修补后的版本(即 exp.sub.func.mod )。
不幸的是,只要您的 exp.sub.func.mod 继续依赖于 prv.sub.func ,就会出现错误:
environment(sub::exp.sub.func)
事实上:
<environment: R_GlobalEnv>
现在返回:<environment: namespace:sub>
,而在修补之前它是{{1}}。
问题是:如何将修补的函数移动到正确的命名空间?
要实现上述问题,您当然可以使用任何包;在我的例子中,我使用 knitr 和 formatR 作为主要和导入的命名空间,并使用 tidy.source()作为修补功能。
答案 0 :(得分:2)
更改 formatR 命名空间中的功能不会更改 knitr 使用的功能,因为 knitr 已加载。所以,你可以卸载并重新加载它。
assignInNamespace("tidy.source", function()print("My tidy.source"), "formatR")
detach('package:knitr', unload=TRUE)
library(knitr)
get("tidy.source", envir=asNamespace("knitr"))
#function()print("My tidy.source")
答案 1 :(得分:2)
如果你想要的只是功能参数后面的评论,我在added中有development version支持,你可以install it from Github。
正如文档所示,通常使用assignInNamespace()
修改包是个坏主意。
答案 2 :(得分:0)
我可能关闭解决方案,但我必须处理未导出的 formatR 函数。实际上,原始的 tidy.source 代码以及修补版本都调用了非导出的包函数,例如: reflow_comments
。
为了说明问题和我遵循的步骤,让我们从测试修补 tidy.source 开始调用私有 formatR 函数。
### Listing 1 - Modified tidy.source
tidy.source.mod=function (source, output, text){
#Print body first line of reflow_comments
head(reflow_comments,1)
}
source, output, text
参数是必要的,因为前面使用了 knit 。
现在我可以使用 tidy.source.mod 修补 tidy.source :
### Listing 2 - Patch tidy.source
## General clean up
if("knitr" %in% loadedNamespaces() ) detach('package:knitr', unload=TRUE)
if("formatR" %in% loadedNamespaces() ) detach('package:formatR', unload=TRUE)
if(exists("tidy.source"))rm(tidy.source)
library("formatR")
## Info
environment(tidy.source )
# <environment: namespace:formatR>
environment(formatR::tidy.source )
# <environment: namespace:formatR>
## Change tidy.source with tidy.source.mod
unlockBinding("tidy.source", env=as.environment("package:formatR"))
assign("tidy.source", tidy.source.mod, envir=as.environment("package:formatR"))
lockBinding("tidy.source", env=as.environment("package:formatR"))
unlockBinding("tidy.source", env=asNamespace ("formatR"))
assign("tidy.source", tidy.source.mod, asNamespace ("formatR") )
environment(tidy.source)= asNamespace( "formatR" )
lockBinding("tidy.source", env=asNamespace ("formatR"))
我们可以查看结果:
### Listing 3 - Check results
getAnywhere(tidy.source)
# A single object matching 'tidy.source' was found
# It was found in the following places
# .GlobalEnv
# package:formatR
# namespace:formatR
# with value
# function (){
# head(reflow_comments,1)
# }
# <environment: namespace:formatR>
tidy.source()
# 1 function (text, idx = grepl("^\\\\s*#+", text), width = getOption("width"))
显然 tidy.source 被 tidy.source.mod 正确替换;命名空间已更新,因此 tidy.source 可以访问(第一行)非导出的reflow_comments
函数。
要处理 knitr ,我们还需要一个要编织的文件,为了简单起见,我在这里使用文本字符串。
### Listing 4 - Sample file/text to knit
library("knitr")
text="
\\documentclass{article}
\\begin{document}
<<comme, include=TRUE>>=
print('hello')
@
\\end{document}
"
knitr 是否能够看到已修补的 tidy.source ?我们可以在R debug 的帮助下进行检查。
debug(knit)
knit(text=text) #will enter debug session (prompt s'd be like 'Browse[2]>')
# debugging in: knit(text = text)
# debug: {
# knit body, very long, omitted
# }
tidy.source #command given inside the debug session
# function (){
# head(reflow_comments,1)
# }
tidy.source() # :-( reflow_comments is not accessible
Q #quit debug session
undebug(knit)
不幸的是,从 knit 可以看到已修补的 tidy.source ,但它无法访问未导出的 formatR 函数,这对于< em> knit 通常可以通过非修补的 tidy.source 来实现。
这里的一些提示可能是formatR::tidy.source()
也不起作用:
formatR::tidy.source()
# Error in head(reflow_comments, 1) (from #2) : object 'reflow_comments' not found
命名空间:formatR 的环境是:
environment(formatR::tidy.source )
# <environment: R_GlobalEnv>
在修补之前是<environment: namespace:formatR>
(参见清单2中的信息)。虽然在修补时很容易重置environment(tidy.source )
,但对于 namespace:formatR ,我们收到错误:
environment(formatR::tidy.source )=asNamespace( "formatR" )
# Error in environment(formatR::tidy.source) = asNamespace("formatR") :
# object 'formatR' not found
我还在寻找......
答案 3 :(得分:0)
这是一种可能的错误尝试,允许在 formatR 包中包含的tidy.source
函数的函数参数之后进行内联注释。
## ============ Possible (wrong?) ideas on inline comments ============
tidy.source.mod= function (source = "clipboard", keep.comment = getOption("keep.comment",
TRUE), keep.blank.line = getOption("keep.blank.line", TRUE),
keep.space = getOption("keep.space", FALSE), replace.assign = getOption("replace.assign",
FALSE), left.brace.newline = getOption("left.brace.newline",
FALSE), reindent.spaces = getOption("reindent.spaces",
4), output = TRUE, text = NULL, width.cutoff = getOption("width"),
...)
{
if (is.null(text)) {
if (source == "clipboard" && Sys.info()["sysname"] ==
"Darwin") {
source = pipe("pbpaste")
}
text = readLines(source, warn = FALSE)
}
if (length(text) == 0L || all(grepl("^\\s*$", text))) {
if (output)
cat("\n", ...)
return(list(text.tidy = "", text.mask = ""))
}
text.lines = text
if (keep.comment) {
if (!keep.space)
text.lines = gsub("^[[:space:]]+|[[:space:]]+$",
"", text.lines)
head.comment = grepl("^[[:space:]]*#", text.lines)
if (any(head.comment)) {
text.lines[head.comment] = gsub("\"", "'", text.lines[head.comment])
}
if (!keep.space) {
head.comment = head.comment & !grepl("^\\s*#+'",
text.lines)
text.lines = reflow_comments(text.lines, head.comment,
width.cutoff)
head.comment = grepl("^[[:space:]]*#", text.lines)
}
text.lines[head.comment] = sprintf("invisible(\"%s%s%s\")",
begin.comment, text.lines[head.comment], end.comment)
blank.line = grepl("^[[:space:]]*$", text.lines)
if (any(blank.line) && keep.blank.line) {
else.line = grep("^[[:space:]]*else(\\W|)", text.lines)
for (i in else.line) {
j = i - 1
while (blank.line[j]) {
blank.line[j] = FALSE
j = j - 1
warning("removed blank line ", j, " (you should not put an 'else' in a separate line!)")
}
}
text.lines[blank.line] = sprintf("invisible(\"%s%s\")",
begin.comment, end.comment)
}
text.lines = mask_inline(text.lines)
}
#modified code
ic=grepl( "%InLiNe_IdEnTiFiEr%", text.lines)
text.lines[ic]=substr(text.lines[ic], 1, nchar(text.lines[ic])-1)
text.lines[ic]= paste0(text.lines[ic], "%InLiNe_IdEnTiFiEr_mod%\"")
#end modified code
text.mask = tidy_block(text.lines, width.cutoff, replace.assign)
text.tidy = if (keep.comment)
unmask.source(text.mask)
else text.mask
text.tidy = reindent_lines(text.tidy, reindent.spaces)
if (left.brace.newline)
text.tidy = move_leftbrace(text.tidy, reindent.spaces)
#modified code
text.tidy= unlist(sapply(text.tidy, strsplit, "%InLiNe_IdEnTiFiEr_mod%", USE.NAMES=FALSE))
#end modified code
if (output)
cat(paste(text.tidy, collapse = "\n"), "\n", ...)
invisible(list(text.tidy = text.tidy, text.mask = text.mask))
}
## ====================================================
## ============ Implementation ============
## Clean-up
if("formatR" %in% loadedNamespaces() ) detach('package:formatR', unload=TRUE)
if(exists("tidy.source"))rm(tidy.source)
library("formatR")
## String with inline comments after arguments
text.input="paste(1 # comm
,7)
"
## The same in vector format
text.input=strsplit(text.input, "\n")[[1]]
## Implementation without patch
tidy.source(text=text.input) #newline removed with wrong result!
# paste(1 # comm, 7)
# Tentative patch
unlockBinding("tidy.source", as.environment("package:formatR") )
assign("tidy.source", tidy.source.mod, pos="package:formatR")
environment(tidy.source)= asNamespace( "formatR" )
## Implementation with patch
tidy.source(text=text.input) # apparently ok:
# paste(1 # comm
# , 7)