r-cli-app について
Rapp は、R スクリプトのトップレベルコードから引数やサブコマンドを推定し、コマンドラインアプリにする R パッケージです。別途パーサー定義を書かずにヘルプや型付きオプションを用意したい R 開発者向けです。
主な機能
- 01R の代入式から引数を推定
- 02オプションと位置引数に対応
- 03switch() からサブコマンドを生成
- 04人間向け・機械向けヘルプを生成
- 05R からスクリプトを実行・テスト
活用シーン
- 01R スクリプトをコマンドラインアプリ化
- 02パッケージスクリプトにオプションとサブコマンドを追加
- 03testthat のスナップショットで CLI 動作をテスト
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ posit-dev/ skills --skill r-cli-appインストール後、Claude Code で/r-cli-appを入力して読み込まれたか確認します。
同梱ファイル
SKILL.md と一緒にインストールされる参考資料やスクリプト。
.evals/convert-script/eval.mdinput-script.Rprompt.md.evals/simple-cli/eval.mdprompt.md.evals/subcommand-tool/eval.mdprompt.mdreferences/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 の構成要素に変換します。つまり、次のとおりです。
- 同じスクリプトが、
source()から実行しても CLI ツールとして実行しても、同じように動作します。 - 通常の R コードを記述するだけで、Rapp が記述内容から CLI を推測します。
- 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 | 可変長位置引数 | 0 個以上の値 |
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)で判定します。 - スネークケースの変数名はケバブケースに対応します:
n_flips→--n-flips。 - 位置引数は常に文字列として渡されるため、手動で変換してください。
スクリプトの構成
Shebang 行
#!/usr/bin/env Rapp
chmod +x の実行後、macOS/Linux ではスクリプトを直接実行できるようになります。
Windows では、Rapp myscript.R を明示的に呼び出してください。
フロントマターのメタデータ
コードより前にあるハッシュパイプコメント(#|)で、スクリプトレベルのメタデータを設定します。
#!/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: | 表示タイトル(サブコマンドとフロントマター用) |
short: | 1 文字のエイリアス。例:'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)などの一般的なフラグに、1 文字のショートカットがあることを期待します。
名前付きオプション
スカラーリテラルの代入は、名前付きオプションになります。
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(機械可読形式)が自動的に追加されます。これらはサブコマンドでも使えます。
開発とテスト
対話型の開発
Rセッションからスクリプトをテストするには、Rapp::run()を使います。
Rapp::run("path/to/myapp.R", c("--help"))
Rapp::run("path/to/myapp.R", c("--name", "Alice", "--count", "5"))
評価環境を(非表示で)返すため、内容を確認できます。また、対話型のデバッグにbrowser()を使用できます。
パッケージ内のCLIアプリのテスト
testthatのスナップショットテストには、Rapp::run()を使います。返された環境にアクセスして計算結果をテストし、出力は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]
完全な例:Todoマネージャー(サブコマンド)
#!/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をお読みください。