R包是否可以向用户的代码片段文件中添加代码片段?

3

有几个代码片段对我的工作流程非常宝贵,并且与我自定义的R包中的函数很好地配合使用。我可以把这些代码片段包含在我的R包中,这样当用户安装我的包时,它们就会被添加到用户的代码片段中(当然需要获得允许)。

创建一个SQL块的Rmd代码片段示例:

snippet sql
    ```{sql, connection = conn, output.var = "${1:df}"}
    ${2}
    ```

1
https://support.rstudio.com/hc/en-us/articles/204463668-Code-Snippets的底部,"保存和共享代码片段"部分可能对您有所帮助。您可以在您的软件包中编写一个函数,使用`cat(..., append = TRUE)`将您的代码片段添加到链接文章中提到的文件的末尾。但是,我不确定您是否可以在安装时执行此操作。 - duckmayr
2个回答

7

简短回答:是的

实现你想要的方式(适用于我的软件包)之一是:

  1. 在软件包的inst/目录中的某个地方存储包裹片段定义的两个文本文件。重要的是,片段要完全遵循格式规则(例如,行首使用制表符而不是空格)。我有一个用于R代码片段的文件和一个用于Markdown的文件。

  2. 定义一个函数,它读取这些文件并将它们的内容复制到RStudio的用户片段文件中。这些文件在第一次尝试编辑片段时生成(Tools -> Global Options -> Code -> Edit Snippets)(我认为RStudio在尝试编辑之前使用其他文件,而非用户公开的文件。)。在ubuntu上,RStudio的文件名为'r.snippets'和'markdown.snippets',位于'~/.R/snippets/'中。我还会检查片段定义是否已存在,并在使用cat(..., append=TRUE)添加软件包片段定义之前再次检查行首的制表符。

  3. 我最初使用了一个包含配置等所有内容的复杂.onLoad函数,但现在我只导出一个addPackageSnippets函数;)

编辑

一些代码:

检查已经存在的片段定义的部分: 我只是读取rstudio文件并提取以“snippet”开头的行。我对软件包片段定义文件执行相同的操作,并使用setdiff(人们可能还想对列表使用trimws,以防存在一些尾随的空格)。

# load package snippets definitions
#
pckgSnippetsFileContent <- readLines(pckgSnippetsFilesPath)

# Extract names of package snippets
#
pckgSnippetsFileDefinitions <- pckgSnippetsFileContent[grepl("^snippet (.*)", pckgSnippetsFileContent)]

# Extract 'names' of already existing snitppets
#
rstudioSnippetsFileContent <- readLines(rstudioSnippetsFilePath)
rstudioSnippetDefinitions <- rstudioSnippetsFileContent[grepl("^snippet (.*)", rstudioSnippetsFileContent)]

# find definitions appearing in packageSnippets but not in rstudioSnippets
# if no snippets are missing go to next file
#
snippetsToCopy <- setdiff(pckgSnippetsFileDefinitions, rstudioSnippetDefinitions)

为了提供背景信息,这是整个“addPackageSnippets”函数。该函数仅使用基本软件包,除了getOS之外,它返回“linux”,“windows”或“mac”中的一个(即Sys.info()的包装器)。

#' @title Export snippets
#'
#' @description \code{addPackageSnippets} copies all (missing) snippet definitions
#'   in 'inst/rstudio/Rsnippets.txt' and 'Rmdsnippets.txt' to the RStudios user snippet location.
#'
#' @return boolean invisible(FALSE) if nothing was added, invisible(TRUE) if snipped definitions were added
#' @export
#'
#' @examples \dontrun{addPackageSnippets()}
addPackageSnippets <- function() {

  added <- FALSE

  # if not on RStudio or RStudioServer exit
  #
  if (!nzchar(Sys.getenv("RSTUDIO_USER_IDENTITY"))) {
    return(NULL)
  }

  # Name of files containing snippet code to copy
  #
  pckgSnippetsFiles <- c("Rsnippets.txt", "Rmdsnippets.txt")

  # Name of files to copy into. Order has to be the same
  # as in 'pckgSnippetsFiles'
  #
  rstudioSnippetsFiles <- c("r.snippets", "markdown.snippets")

  # Path to directory for RStudios user files depends on OS
  #
  if (getOS() == "linux") {
    rstudioSnippetsPathBase <- "~/.R/snippets"
  } else if (getOS() == "windows") {
    rstudioSnippetsPathBase <- file.path(path.expand('~'), ".R", "snippets")
  } else {
    warning(paste0("goSnippets() is only implemented on linux and windows"))
    return(NULL)
  }

  # Read each file in pckgSnippetsFiles and add its contents
  #
  for (i in seq_along(pckgSnippetsFiles)) {

    # Try to get template, if template is not found skip it
    #
    pckgSnippetsFilesPath <- system.file("rstudio", pckgSnippetsFiles[i], package = "myFunc")
    if (pckgSnippetsFilesPath == "") {
      next()
    }

    # load package snippets definitions
    #
    pckgSnippetsFileContent <- readLines(pckgSnippetsFilesPath)

    # Extract names of package snippets
    #
    pckgSnippetsFileDefinitions <- pckgSnippetsFileContent[grepl("^snippet (.*)", pckgSnippetsFileContent)]


    # Construct path for destination file
    #
    rstudioSnippetsFilePath <- file.path(rstudioSnippetsPathBase, rstudioSnippetsFiles[i])

    # If targeted RStudios user file does not exist, raise error (otherwise we would 'remove')
    # the default snippets from the 'user file'
    #
    if (!file.exists(rstudioSnippetsFilePath)) {
      stop(paste0( "'", rstudioSnippetsFilePath, "' does not exist yet\n.",
                   "Use RStudio -> Tools -> Global Options -> Code -> Edit Snippets\n",
                   "To initalize user defined snippets file by adding dummy snippet\n"))
    }

    # Extract 'names' of already existing snitppets
    #
    rstudioSnippetsFileContent <- readLines(rstudioSnippetsFilePath)
    rstudioSnippetDefinitions <- rstudioSnippetsFileContent[grepl("^snippet (.*)", rstudioSnippetsFileContent)]

    # replace two spaces with tab, ONLY at beginning of string
    #
    pckgSnippetsFileContentSanitized <- gsub("(?:^ {2})|\\G {2}|\\G\t", "\t", pckgSnippetsFileContent, perl = TRUE)

    # find defintions appearing in packageSnippets but not in rstudioSnippets
    # if no snippets are missing go to next file
    #
    snippetsToCopy <- setdiff(trimws(pckgSnippetsFileDefinitions), trimws(rstudioSnippetDefinitions))
    snippetsNotToCopy <- intersect(trimws(pckgSnippetsFileDefinitions), trimws(rstudioSnippetDefinitions))
    if (length(snippetsToCopy) == 0) {
      # cat(paste0("(\nFollowing snippets will NOT be added because there is already a snippet with that name: ",
      #            paste0(snippetsNotToCopy, collapse=", ") ,")"))
      next()
    }

    # Inform user about changes, ask to confirm action
    #
    if (interactive()) {
      cat(paste0("You are about to add the following ", length(snippetsToCopy),
                 " snippets to '", rstudioSnippetsFilePath, "':\n",
                 paste0(paste0("-", snippetsToCopy), collapse="\n")))
      if (length(snippetsNotToCopy) > 0) {
        cat(paste0("\n(The following snippets will NOT be added because there is already a snippet with that name:\n",
                   paste0(snippetsNotToCopy, collapse=", ") ,")"))
      }
      answer <- readline(prompt="Do you want to procedd (y/n): ")
      if (substr(answer, 1, 1) == "n") {
        next()
      }
    }

    # Create list of line numbers where snippet definitons start
    # This list is used to determine the end of each definition block
    #
    allPckgSnippetDefinitonStarts <- grep("^snippet .*", pckgSnippetsFileContentSanitized)

    for (s in snippetsToCopy) {
      startLine <- grep(paste0("^", s, ".*"), pckgSnippetsFileContentSanitized)

      # Find last line of snippet definition:
      # First find start of next defintion and return
      # previous line number or lastline if already in last definiton
      #
      endLine <- allPckgSnippetDefinitonStarts[allPckgSnippetDefinitonStarts > startLine][1] -1
      if (is.na(endLine)) {
        endLine <- length(pckgSnippetsFileContentSanitized)
      }

      snippetText <- paste0(pckgSnippetsFileContentSanitized[startLine:endLine], collapse = "\n")

      # Make sure there is at least one empty line between entries
      #
      if (tail(readLines(rstudioSnippetsFilePath), n=1) != "") {
        snippetText <- paste0("\n", snippetText)
      }

      # Append snippet block, print message
      #
      cat(paste0(snippetText, "\n"), file = rstudioSnippetsFilePath, append = TRUE)
      cat(paste0("* Added '", s, "' to '", rstudioSnippetsFilePath, "'\n"))
      added <- TRUE
    }
  }

  if (added) {
    cat("Restart RStudio to use new snippets")
  }

  return(invisible(added))

}

你能分享一下代码,展示如何检查现有的代码片段以避免重复吗? - Joe
1
@Joe 我添加了检查现有代码片段的部分以及整个函数。如果需要更多帮助,请在这里提问。 - dario
做得好!从来没有想过那样做!感谢你的问题和答案! - HolgerBarlt
非常出色的回答。小建议:我在readLines()内使用了warn=FALSE,这样就不会收到不完整的最后一行警告信息了。 - DeanAttali
我真的很喜欢这个答案,尽管我认为它需要在使用它的每个包中进行定义和导出。将其添加到devtools(可能?)中作为一个带有包名称参数或指向GitHub存储库的URL的拉取请求将是一个很好的选择。 - Nate Hall

3

对于任何看到这个帖子的人,为了补充Dario的很好的回答:从RStudio v1.3开始,文件路径已经更改。因此,在他的函数中,用于设置rstudioSnippetsPathBase的部分需要更改为以下内容:

if (rstudioapi::versionInfo()$version < "1.3") {
    rstudioSnippetsPathBase <- file.path(path.expand('~'),".R", "snippets")
  } else {
    if (.Platform$OS.type == "windows") {
      rstudioSnippetsPathBase <- file.path(Sys.getenv("APPDATA"), "RStudio", "snippets")
    } else {
      rstudioSnippetsPathBase <- file.path(path.expand('~'), ".config/rstudio", "snippets")
    }
  }

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