【问题标题】:Extract comments from R source files, keep function in which they occurs从 R 源文件中提取注释,保留它们出现的函数
【发布时间】:2015-09-18 11:53:47
【问题描述】:

我想从我的 R 源脚本中提取 cmets(与模式匹配),以保留它们出现的函数。

目标是使用经典的降价复选框 - [ ]- [x] 在函数体代码中编写文档 cmets,然后提取这些 cmets 以作为字符向量列表进行进一步处理 - 我可以轻松地将其写入新的 .md 文件。

下面的可重现示例和预期输出。

# preview the 'data'
script_body = c('# some init comment - not matching pattern','g = function(){','# - [x] comment_g1','# - [ ] comment_g2','1','}','f = function(){','# - [ ] comment_f1','# another non match to pattern','g()+1','}')
cat(script_body, sep = "\n")
# # some init comment - not matching pattern
# g = function(){
#     # - [x] comment_g1
#     # - [ ] comment_g2
#     1
# }
# f = function(){
#     # - [ ] comment_f1
#     # another non match to pattern
#     g()+1
# }

# populate R souce file
writeLines(script_body, "test.R")

# test it 
source("test.R")
f()
# [1] 2

# expected output
r = magic_function_get_comments("test.R", starts.with = c(" - [x] "," - [ ] "))
# r = list("g" = c(" - [x] comment_g1"," - [ ] comment_g2"), "f" = " - [ ] comment_f1")
str(r)
# List of 2
#  $ g: chr [1:2] " - [x] comment_g1" " - [ ] comment_g2"
#  $ f: chr " - [ ] comment_f1"

【问题讨论】:

    标签: r knitr


    【解决方案1】:

    这是 hrbmstr 所做的一个精简的、未经评估的变体:

    get_comments = function (filename) {
        is_assign = function (expr)
            as.character(expr) %in% c('<-', '<<-', '=', 'assign')
    
        is_function = function (expr)
            is.call(expr) && is_assign(expr[[1]]) && is.call(expr[[3]]) && expr[[3]][[1]] == quote(`function`)
    
        source = parse(filename, keep.source = TRUE)
        functions = Filter(is_function, source)
        fun_names = as.character(lapply(functions, `[[`, 2))
        setNames(lapply(attr(functions, 'srcref'), grep,
                        pattern = '^\\s*#', value = TRUE), fun_names)
    }
    

    这里有一个警告:由于我们不评估源代码,我们可能会错过函数定义(例如,我们不会找到 f = local(function (x) x))。上面的函数使用简单的启发式方法来查找函数定义(它查看所有将function 表达式简单地赋值给变量的方法)。

    这只能使用eval(或source)来解决,它有自己的警告——例如,执行来自未知来源的文件存在安全风险。

    【讨论】:

    • jangorecki - 你应该接受这个作为答案,这样我就可以删除我的(Konrad 是一个更好的解决方案)
    • @hrbrmstr 不同意。
    • @hrbrmstr 我会接受这个但不要删除你的,它回答了这个问题,很好,它教人,我也认为它很有价值!
    • “这是一个安全风险……” 鉴于 data.table 在他们的 CRAN 包小插曲(sourcelibrary 等)中使用跟踪代码所做的事情是IMO 普遍存在安全风险。
    • 谢谢两位。我制作了一个包装器,以便更容易地用于包开发。描述在博客here
    【解决方案2】:

    不太可能有人会为您编写 grep / stringr::str_match 部分(这不是繁重的代码编写服务)。但是,迭代解析的函数源的习惯用法可能对更广泛的受众有用,值得包含。

    CAVEAT 这个source()s 是.R 文件,意思是它评估它。

    #' Extract whole comment lines from an R source file
    #'
    #' \code{source()} an R source file into a temporary environment then
    #' iterate over the source of \code{function}s in that environment and
    #' \code{grep} out the whole line comments which can then be further
    #' processed.
    #' 
    #' @param source_file path name to source file that \code{source()} will accept
    extract_comments <- function(source_file) {
    
      tmp_env <- new.env(parent=sys.frame())
      source(source_file, tmp_env, echo=FALSE, print.eval=FALSE, verbose=FALSE, keep.source=TRUE)
      funs <- Filter(is.function, sapply(ls(tmp_env), get, tmp_env))
    
      lapply(funs, function(f) {
        # get function source
        function_source <- capture.output(f)
        # only get whole line comment lines
        comments <- grep("^[[:blank:]]*#", function_source, value=TRUE)
        # INCANT YOUR GREP/REGEX MAGIC HERE 
        # instead of just returning the comments
        # since this isn't a free code-writing service
        comments
      })
    
    }
    
    str(extract_comments("test.R"))
    ## List of 2
    ##  $ f: chr [1:2] "# - [ ] comment_f1" "# another non match to pattern"
    ##  $ g: chr [1:2] "# - [x] comment_g1" "# - [ ] comment_g2"
    

    【讨论】:

    • “我这样做是因为 source() 保留 cmets 而 parse 没有。” ——不,那不是真的,他们的行为是一样的。其实source在内部使用parse
    • 我的简洁尝试可能是没有根据的(我知道它保留了它,但通过 parse 访问 cmets 是一种痛苦)。我会重新措辞。
    • 评估不是问题,因此您的解决方案是完美的,我将自己处理正则表达式 - 同意您对此的评论。谢谢!
    • @hrbrmstr 我没有发现表达式比评估函数更复杂(请参阅我的答案),但答案在其他方面很好。一个巨大的警告:你的source 是非本地的,所以它会将东西转储到全局命名空间中。让它在一个空的环境中进行评估。 Also, source has a bug under Windows 因为它无法处理 UTF-8。我知道的唯一解决方法是使用eval(parse(…)) 而不是source(…)
    • gd 点。评论被删除,环境污染局部化。谢谢
    猜你喜欢
    • 2021-11-28
    • 2021-12-11
    • 2010-10-20
    • 1970-01-01
    • 2021-03-08
    • 1970-01-01
    • 2022-07-07
    • 1970-01-01
    • 2013-11-19
    相关资源
    最近更新 更多