mcpField
R
r-cli-app

r-cli-app

github.com/posit-dev開発551691 回インストール今日更新
リポジトリ

r-cli-app について

Rapp は、R スクリプトのトップレベルコードから引数やサブコマンドを推定し、コマンドラインアプリにする R パッケージです。別途パーサー定義を書かずにヘルプや型付きオプションを用意したい R 開発者向けです。

主な機能

  1. 01R の代入式から引数を推定
  2. 02オプションと位置引数に対応
  3. 03switch() からサブコマンドを生成
  4. 04人間向け・機械向けヘルプを生成
  5. 05R からスクリプトを実行・テスト

活用シーン

  1. 01R スクリプトをコマンドラインアプリ化
  2. 02パッケージスクリプトにオプションとサブコマンドを追加
  3. 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.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可変長位置引数0 個以上の値
foo <- c()繰り返し可能な --foo文字列として複数の値を指定
foo <- list()繰り返し可能な --fooYAML/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をお読みください。

コメント