5

我正在尝试覆盖包的tidy.source功能knitr。问题是tidy.source在包中定义的formatR,它是由包导入的knitr。如果我运行:

get("tidy.source", envir=asNamespace("knitr"))

我得到了原始代码。所以我很想覆盖tidy.source

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没有任何变化。

有什么帮助吗?

编辑:

由于knitr/formatR的新开发版本,此问题部分已过时。非常感谢 Yihui 注意到这个讨论并决定更新他的包。看:

https://github.com/yihui/formatR/commit/6f70360f359caa8d2bb33190a1c89530defb0e98

我绝对可以从Sweave切换到knitr

关于覆盖导入包函数的一般问题仍然悬而未决。由于它不再与knitr/formatR包相关,我用更笼统的术语重申它。

假设您有一个包main导入包imp。如果加载前者,"package:main"则显示在附加包列表中,"main""sub"显示在加载的名称空间的名称中。

假设main导入导出的子函数exp.sub.func ,它依次调用未导出子函数prv.sub.func 。如果您想使用 exp.sub.func.mod 更改/自定义exp.sub.func 您可能会考虑使用:

assign("exp.sub.func", exp.sub.func.mod, asNamespace ("sub"))

结果,通过运行,sub::exp.sub.func您将获得修补后的版本(即exp.sub.func.mod)。
不幸的是,就您的exp.sub.func.mod继续依赖prv.sub.func 而言,您会收到错误消息:

Error in [...] : object 'prv.sub.func' not found

实际上:

environment(sub::exp.sub.func) 

现在返回:<environment: R_GlobalEnv><environment: namespace:sub>修补之前。

问题是:如何将修补的函数移动到正确的命名空间

要解决上述问题,您当然可以使用任何软件包;在我的例子中,我使用knitrformatR作为主要和导入的命名空间和tidy.source()作为修补函数。

4

4 回答 4

3

更改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")
于 2012-11-28T02:01:07.073 回答
2

如果你想要的只是函数参数后的注释,我已经在开发版中添加了支持,你可以从 Github 安装它

assignInNamespace()如其文档所示,通常使用 修改包是一个坏主意。

于 2012-11-30T21:47:37.280 回答
0

这是一个可能的错误尝试,允许在formatRtidy.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) 
于 2012-11-30T13:53:37.017 回答
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函数,这对于knit通常可以通过未修补的tidy.source 进行

这里的一些提示可能也formatR::tidy.source()不起作用:

formatR::tidy.source()
# Error in head(reflow_comments, 1) (from #2) : object 'reflow_comments' not found

namespace: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 

我还在寻找......

于 2012-11-30T03:16:41.753 回答