mcpField
R
r-cli-app

r-cli-app

github.com/posit-dev开发551691 次安装今天更新
代码仓库

关于 r-cli-app

Rapp 是用于将 R 脚本转为命令行应用的 R 包,可从顶层代码推断参数和子命令。它面向希望获得帮助文本和类型化选项、又不想单独编写解析定义的 R 开发者。

主要功能

  1. 01从 R 赋值推断命令行参数
  2. 02支持选项与位置参数
  3. 03从 switch() 生成子命令
  4. 04生成可读及机器可读帮助
  5. 05在 R 中运行和测试脚本

适用场景

  1. 01将 R 脚本转换为命令行应用
  2. 02为包脚本添加选项和子命令
  3. 03使用 testthat 快照测试命令行行为

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/posit-dev/skills --skill r-cli-app

安装后在 Claude Code 中输入/r-cli-app确认已加载。

包含的文件

除 SKILL.md 外,安装时一起下载的参考资料和脚本。

.evals/convert-script/
eval.md
input-script.R
prompt.md
.evals/simple-cli/
eval.md
prompt.md
.evals/subcommand-tool/
eval.md
prompt.md
references/
advanced.md

详细介绍

使用 Rapp 构建 CLI 应用

Rapp(v0.3.0)是一个 R 软件包,可直接替代 Rscript,并自动将命令行参数解析为 R 值。它能将简单的 R 脚本变成完善的 CLI 应用,支持参数解析、帮助文本和子命令,无需编写任何样板代码。

R ≥ 4.1.0 | CRAN: install.packages("Rapp") | GitHub: r-lib/Rapp

安装后,将 Rapp 启动器添加到 PATH:

Rapp::install_pkg_cli_apps("Rapp")

这会将 Rapp 可执行文件放在 ~/.local/bin(macOS/Linux)或 %LOCALAPPDATA%\Programs\R\Rapp\bin(Windows)中。


核心概念:脚本就是规范

Rapp 会扫描 R 脚本中的顶层表达式,并将特定模式转换为 CLI 构造。这意味着:

  1. 同一个脚本通过 source() 调用和作为 CLI 工具运行时,行为完全一致。
  2. 你只需编写普通的 R 代码,Rapp 会根据你编写的内容推断 CLI。
  3. R 代码中的默认值会成为 CLI 的默认值。

只有顶层赋值会被识别。函数、循环或条件语句中的赋值不会被解析为 CLI 参数。


模式识别:R → CLI 映射

下表是 Rapp 的核心:每种 R 模式都会自动映射到相应的 CLI 界面:

R 顶层表达式CLI 界面说明
foo <- "text"--foo <value>字符串选项
foo <- 1L--foo <int>整数选项
foo <- 3.14--foo <float>浮点数选项
foo <- TRUE / FALSE--foo / --no-foo布尔开关
foo <- NA_integer_--foo <int>可选整数(NA = 未设置)
foo <- NA_character_--foo <str>可选字符串(NA = 未设置)
foo <- NULL位置参数默认必填
foo... <- NULL可变位置参数零个或多个值
foo <- c()可重复的 --foo多个字符串值
foo <- list()可重复的 --foo多个按 YAML/JSON 解析的值
switch("", cmd1={}, cmd2={})子命令app cmd1、app cmd2
switch(cmd <- "", ...)子命令相同;会将命令名称保存在 cmd 中
类型行为
  • 非字符串标量会在 CLI 中按 YAML/JSON 解析,并转换为默认值的 R 类型。n <- 5L 表示 --n 10 会得到整数 10L。
  • NA 默认值表示参数可选。使用 !is.na(myvar) 进行判断。
  • 蛇形命名变量名会映射为 kebab-case:n_flips → --n-flips。
  • 位置参数始终以字符字符串形式传入,需手动转换。

脚本结构

Shebang 行
#!/usr/bin/env Rapp

运行 chmod +x 后,可在 macOS/Linux 上直接执行脚本。在 Windows 上,请显式调用 Rapp myscript.R。

Front matter 元数据

在任何代码之前添加 Hash-pipe 注释(#|),即可设置脚本级元数据:

#!/usr/bin/env Rapp
#| name: my-app
#| title: My App
#| description: |
#|   A short description of what this app does.
#|   Can span multiple lines using YAML block scalar `|`.

name: 字段用于设置帮助输出中的应用名称(默认为文件名)。

各参数注释

将 #| 注释放在所注释赋值语句的正前方:

#| description: Number of coin flips
#| short: 'n'
flips <- 1L

可用的注释字段:

字段用途
description:在 --help 中显示的帮助文本
title:显示标题(用于子命令和 front matter)
short:单字母别名,例如 'n' → -n
required:true/false — 仅用于位置参数
val_type:覆盖类型:string、integer、float、bool、any
arg_type:覆盖 CLI 类型:option、switch、positional
action:用于可重复选项:replace 或 append

为常用选项添加 #| short:——用户通常期望常用标志(如 verbose(-v)、output(-o)或 count(-n))有单字母快捷方式。


命名选项

标量字面量赋值会变成命名选项:

name <- "world"          # --name <value>    (string, default "world")
count <- 1L              # --count <int>     (integer, default 1)
threshold <- 0.5         # --threshold <flt> (float, default 0.5)
seed <- NA_integer_      # --seed <int>      (optional, NA if omitted)
output <- NA_character_  # --output <str>    (optional, NA if omitted)

对于可选参数,请检查用户是否提供了该参数:

seed <- NA_integer_
if (!is.na(seed)) set.seed(seed)

布尔开关

TRUE/FALSE 赋值会变成切换开关:

verbose <- FALSE   # --verbose or --no-verbose
wrap <- TRUE       # --wrap (default) or --no-wrap

值 yes/true/1 会将其设为 TRUE;no/false/0 会将其设为 FALSE。

可重复选项

pattern <- c()     # --pattern '*.csv' --pattern 'sales-*'  → character vector
threshold <- list() # --threshold 5 --threshold '[10,20]'   → list of parsed values

位置参数

位置参数默认必填,使用 NULL 赋值:

#| description: The input file to process.
input_file <- NULL

添加 #| required: false 可将其设为可选。使用 is.null(myvar) 进行测试。

可变位置参数

使用 ... 后缀收集多个位置值:

pkgs... <- c()
# install-pkgs dplyr ggplot2 tidyr → pkgs... = c("dplyr", "ggplot2", "tidyr")

子命令

使用以字符串为第一个参数的 switch() 声明子命令。 switch() 之前的选项是全局选项;分支内的选项则是该子命令的 局部选项。

switch(
  command <- "",

  #| title: Display the todos
  list = {
    #| description: Max entries to display (-1 for all).
    limit <- 30L
    # ... list implementation
  },

  #| title: Add a new todo
  add = {
    #| description: Task description to add.
    task <- NULL
    # ... add implementation
  },

  #| title: Mark a task as completed
  done = {
    #| description: Index of the task to complete.
    index <- 1L
    # ... done implementation
  }
)

帮助信息按作用域显示:myapp --help 会列出命令;myapp list --help 会显示 list 专属选项以及全局选项。要嵌套子命令,可在某个分支内再放置一个 switch()。


内置帮助

每个 Rapp 都会自动获得 --help(人类可读)和 --help-yaml(机器可读)。 它们也适用于子命令。


开发与测试

交互式开发

使用 Rapp::run() 在 R 会话中测试脚本:

Rapp::run("path/to/myapp.R", c("--help"))
Rapp::run("path/to/myapp.R", c("--name", "Alice", "--count", "5"))

它会返回求值环境(不可见),供检查使用,并且支持使用 browser() 进行交互式调试。

在软件包中测试 CLI 应用

将 Rapp::run() 与 testthat 快照测试结合使用。通过访问返回的环境来测试计算结果,并使用 expect_snapshot() 测试输出。

请参阅 references/advanced.md,了解详细的测试模式,包括:

  • 通过求值环境访问计算结果
  • 对帮助输出和格式化文本进行快照测试
  • 测试文件副作用和状态变化

完整示例:抛硬币

#!/usr/bin/env Rapp
#| name: flip-coin
#| description: |
#|   Flip a coin.

#| description: Number of coin flips
#| short: 'n'
flips <- 1L

sep <- " "
wrap <- TRUE

seed <- NA_integer_
if (!is.na(seed)) {
  set.seed(seed)
}

cat(sample(c("heads", "tails"), flips, TRUE), sep = sep, fill = wrap)
flip-coin            # heads
flip-coin -n 3       # heads tails heads
flip-coin --seed 42 -n 5
flip-coin --help

生成的帮助信息:

Usage: flip-coin [OPTIONS]

Flip a coin.

Options:
  -n, --flips <FLIPS>  Number of coin flips [default: 1] [type: integer]
      --sep <SEP>      [default: " "] [type: string]
      --wrap / --no-wrap  [default: true]
      --seed <SEED>    [default: NA] [type: integer]

完整示例:待办事项管理器(子命令)

#!/usr/bin/env Rapp
#| name: todo
#| description: Manage a simple todo list.

#| description: Path to the todo list file.
#| short: s
store <- ".todo.yml"

switch(
  command <- "",

  list = {
    #| description: Max entries to display (-1 for all).
    limit <- 30L

    tasks <- if (file.exists(store)) yaml::read_yaml(store) else list()
    if (!length(tasks)) {
      cat("No tasks yet.\n")
    } else {
      if (limit >= 0L) tasks <- head(tasks, limit)
      writeLines(sprintf("%2d. %s\n", seq_along(tasks), tasks))
    }
  },

  add = {
    #| description: Task description to add.
    task <- NULL

    tasks <- if (file.exists(store)) yaml::read_yaml(store) else list()
    tasks[[length(tasks) + 1L]] <- task
    yaml::write_yaml(tasks, store)
    cat("Added:", task, "\n")
  },

  done = {
    #| description: Index of the task to complete.
    #| short: i
    index <- 1L

    tasks <- if (file.exists(store)) yaml::read_yaml(store) else list()
    task <- tasks[[as.integer(index)]]
    tasks[[as.integer(index)]] <- NULL
    yaml::write_yaml(tasks, store)
    cat("Completed:", task, "\n")
  }
)
todo add "Write quarterly report"
todo list
todo list --limit 5
todo done 1
todo --store /tmp/work.yml list

在 R 软件包中发布 CLI

将 CLI 脚本放在 exec/ 中,并在 DESCRIPTION 的 Imports 中添加 Rapp:

mypkg/
├── DESCRIPTION
├── R/
├── exec/
│   ├── myapp       # script with #!/usr/bin/env Rapp shebang
│   └── myapp2
└── man/

用户安装软件包后,即可安装 CLI 启动器:

Rapp::install_pkg_cli_apps("mypkg")

提供一个便捷安装器,让用户无需了解 Rapp:

#' Install mypkg CLI apps
#' @export
install_mypkg_cli <- function(destdir = NULL) {
  Rapp::install_pkg_cli_apps(package = "mypkg", destdir = destdir)
}

默认情况下,启动器会设置 --default-packages=base,<pkg>,因此只有 base 和该软件包会自动加载。其他依赖项请使用 library()。


快速参考:常见模式

可选参数使用 NA 还是 NULL
  • NA(NA_integer_、NA_character_)→ 可选的命名选项。 测试:!is.na(x)。
  • NULL + #| required: false → 可选的位置参数。 测试:!is.null(x)。
stdin/stdout
input_file <- NA_character_
con <- if (is.na(input_file)) file("stdin") else file(input_file, "r")
lines <- readLines(con)
writeLines(lines, stdout())
退出代码和 stderr
message("Error: something went wrong")   # writes to stderr
cat("Error:", msg, "\n", file = stderr()) # also stderr
quit(status = 1)                          # non-zero exit
错误处理
tryCatch({
  result <- do_work()
}, error = function(e) {
  cat("Error:", conditionMessage(e), "\n", file = stderr())
  quit(status = 1)
})

更多参考资料

对于较少见的主题——启动器自定义(#| launcher: 前置元数据)、详细的 Rapp::install_pkg_cli_apps() API 选项,以及更完整的示例 (去重筛选器、可变参数 install-pkg、交互式回退)——请阅读 references/advanced.md。

评论

支持 Markdown