rubycli 0.1.2 → 0.1.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 14562bc06b2479369c2951a54cac6f8f502e5ff81ae453b25bdb225b2017f671
4
- data.tar.gz: 731a6704bac4ca7d9471b4ff00d7476745e81c442666e99ca3b710d70c9e4622
3
+ metadata.gz: 9287ea2ca51772eb53c50fe717e648c7d23a9fb1e5cb0e25990eef7f07cde225
4
+ data.tar.gz: aab6e71f2beafb924bb4bc29689c6ce64391d55bc43936a789278ad764e91592
5
5
  SHA512:
6
- metadata.gz: 2ef973534a268ac8475f21f9d26ef16202ab5f4a89a69c3ea94a0700f1d89a5b17e533d5e821f8c6833855c91f72a5d5d6fcbfb75716c9a3648bf52e9c26600b
7
- data.tar.gz: c2cc78a8dbc2ec9185c6fcb598ea5be2160ea336021932e2ab7ffb58b5b2be5f8fad078ed379a26a33c66241ed69a17c1edd9bd852663299747a630edd9da235
6
+ metadata.gz: 98baa8a62366c9babedda68174dbc1fadaafbdf1ea4354393bf1a525b9b1d1f2ce0459f20ef8aff81cc4a3448f0686da9bf7940a8fde2526a3b42caf3d2feb63
7
+ data.tar.gz: ac5044eec17148e3bb26ef1aa2fd67b78e6578acd6693747bb9a95e8d043e1842f9ebc853e6e50297130d0351fdfb9cfad04c1d217738c6462eda94c1c5b039f
data/CHANGELOG.md CHANGED
@@ -1,5 +1,49 @@
1
1
  # Changelog
2
2
 
3
+ ## [0.1.5] - 2025-11-10
4
+
5
+ ### Added
6
+ - `rubycli --check` gained a short `-c` alias and refuses to run target commands while linting, making documentation checks easier to script.
7
+ - Bundled `examples/strict_choices_demo.rb` and `examples/typed_arguments_demo.rb` now illustrate literal choice validation and stdlib type coercions.
8
+
9
+ ### Changed
10
+ - Runtime validation now reads the documented literal choices and inferred types for both positional and keyword arguments; invalid values emit `[WARN] …` guidance by default and raise `Rubycli::ArgumentError` under `--strict` with friendly suggestions.
11
+ - Help output renders positional arguments and options as structured tables showing requirement level, types, defaults, and descriptions for quicker scanning.
12
+ - Documentation comments are aligned with the actual method signature, and mismatches surface with file/line context during `rubycli --check`.
13
+ - CLI warnings/errors are prefixed with `[WARN]` / `[ERROR]`, and the deprecated `--debug` flag was removed in favor of `RUBYCLI_DEBUG=true`.
14
+
15
+ ### Fixed
16
+ - `--check` now rejects forwarded CLI arguments as well as JSON/Eval modes, ensuring documentation linting never executes user code and always starts from a clean issue list.
17
+ - Placeholder parsing keeps option descriptions such as `--prefix` in the documentation showcase so README snippets and live behavior stay in sync.
18
+
19
+ ## [0.1.4] - 2025-11-08
20
+
21
+ ### Changed
22
+ - Re-cut the release that was briefly published as 0.1.3 (and yanked) so RubyGems now hosts the complete set of changes listed below.
23
+
24
+ > _Note:_ 0.1.3 was yanked before general availability; consumers should upgrade directly to 0.1.4.
25
+
26
+ ### Documentation
27
+ - Clarified repeated-value guidance (type enforcement, eval mode workflows) and updated both READMEs to reflect the retirement of `(type: …)` annotations while preserving the historical changelog entry.
28
+
29
+ ## [0.1.3] - 2025-11-08
30
+
31
+ ### Added
32
+ - TracePoint-backed constant capture so Rubycli can detect CLI classes/modules even when they are defined indirectly; bundled example and tests illustrate the behavior.
33
+ - Strict/auto constant selection modes with the new `--auto-target` / `-a` flag so single callable constants are picked automatically when requested.
34
+ - `--eval-lax` / `-E` argument mode that evaluates Ruby inputs but gracefully falls back to raw strings on parse failures.
35
+
36
+ ### Changed
37
+ - Argument parsing internals were modularized so JSON/eval coercion now flows through a dedicated controller, simplifying future extensions.
38
+ - `rubycli` now returns explicit status codes for success and failure, improving scriptability.
39
+ - CLI constant selection errors provide clearer guidance, document the `--new` behavior, and consistently refer to the `--auto-target` flag.
40
+
41
+ ### Documentation
42
+ - Clarified the authoritative source for documentation comments, reorganized helper logic in the showcase example, and expanded guidance around constant modes.
43
+
44
+ ### Fixed
45
+ - CLI no longer dumps a Ruby backtrace when `Rubycli::Runner` reports user-facing errors; only the curated guidance is shown.
46
+
3
47
  ## [0.1.2] - 2025-11-06
4
48
 
5
49
  ### Added
data/README.ja.md CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  ![Rubycli ロゴ](assets/rubycli-logo.png)
4
4
 
5
- Rubycli は Ruby のクラス/モジュールに書いたコメントから CLI を自動生成する小さなフレームワークです。Python Fire にインスパイアされていますが、互換や公式ポートを目指すものではありません。Ruby のコメント記法と型アノテーションに合わせて設計しています。
5
+ Rubycli は Ruby のクラス/モジュールにある公開メソッドの定義と、そのメソッドに付けたドキュメントコメントから CLI を自動生成する小さなフレームワークです。Python Fire にインスパイアされていますが、互換や公式ポートを目指すものではありません。Ruby のコメント記法と型アノテーションに合わせて設計しており、コメントに書いた型ヒントや繰り返し指定が CLI の引数解釈もコントロールします。
6
6
 
7
7
  > English guide is available in [README.md](README.md).
8
8
 
@@ -33,7 +33,6 @@ Available commands:
33
33
  greet <NAME>
34
34
 
35
35
  Detailed command help: hello_app.rb COMMAND help
36
- Enable debug logging: --debug or RUBYCLI_DEBUG=true
37
36
  ```
38
37
 
39
38
  ```bash
@@ -107,7 +106,6 @@ Available commands:
107
106
  greet <NAME> [--shout]
108
107
 
109
108
  Detailed command help: hello_app_with_docs.rb COMMAND help
110
- Enable debug logging: --debug or RUBYCLI_DEBUG=true
111
109
  ```
112
110
 
113
111
  ```bash
@@ -147,11 +145,24 @@ end
147
145
 
148
146
  `ruby hello_app.rb ...` の形で呼び出したい場合だけ `require "rubycli"` を追加し、`Rubycli.run` に制御を渡します(後述のクイックスタート参照)。
149
147
 
148
+ ## 定数解決モード
149
+
150
+ Rubycli は「ファイル名を CamelCase にした定数」を公開対象だと想定しています。ファイル名とクラス/モジュール名が一致しない場合は、次のモードで挙動を切り替えられます。
151
+
152
+ | モード | 有効化方法 | 挙動 |
153
+ | --- | --- | --- |
154
+ | `strict`(デフォルト) | 何もしない / `RUBYCLI_AUTO_TARGET=strict` | CamelCase が一致しないとエラーになります。検出した定数一覧と再実行コマンド例を表示します。 |
155
+ | `auto` | `--auto-target`(短縮 `-a`) または `RUBYCLI_AUTO_TARGET=auto` | ファイル内で CLI として実行できる定数が 1 つだけなら自動選択します。複数あれば従来通りエラーで案内します。 |
156
+
157
+ 大規模なコードベースでも安全側を保ちながら、どうしても自動選択したいときだけ 1 フラグで切り替えられます。
158
+
159
+ > **インスタンスメソッド専用のクラスについて** – 公開メソッドがインスタンス側(`def greet` など)にしか無い場合は、`--new` を付けて事前にインスタンス化しないと CLI から呼び出せません。クラスメソッドを 1 つ用意するか、`--new` を明示して実行してください。
160
+
150
161
  ## 開発方針
151
162
 
152
163
  - **便利さが最優先** – 既存の Ruby スクリプトを最小の手間で CLI 化できることを目的にしており、Python Fire の完全移植は目指していません。
153
164
  - **インスパイアであってポートではない** – アイデアの出自は Fire ですが、同等機能を揃える予定は基本的にありません。Fire 由来の未実装機能は仕様です。
154
- - **コードが一次情報、コメントは補助**メソッド定義こそが真実であり、コメントはヘルプを豊かにする付加情報です。コメントと実装のズレを観測したいときだけ `RUBYCLI_STRICT=ON` で厳格モードを有効化し、警告を受け取ります。
165
+ - **メソッド定義が土台、コメントが挙動を補強**公開メソッドのシグネチャが CLI に露出する範囲と必須/任意を決めますが、コメントに `TAG...` や `[Integer]` を書くと同じ引数でも配列化や型変換が行われます。さらに Rubycli は `--names='["Alice","Bob"]'` のような JSON/YAML らしい入力を自動的に安全なリテラルとして評価します。`rubycli --check パス/対象.rb` でコメントと実装のズレを検査しつつ、通常実行時に `--strict` を付ければドキュメント通りでない入力をその場でエラーにできます。(現状の `--check` は「コメントが揃っているか」を中心にチェックしており、`Booalean` のように型トークンを誤記しても検出できません。)
155
166
  - **軽量メンテナンス** – 実装の多くは AI 支援で作られており、深い Ruby メタプログラミングを伴う大規模拡張は想定外です。Fire 互換を求める PR は事前相談をお願いします。
156
167
 
157
168
  ## 特徴
@@ -160,19 +171,19 @@ end
160
171
  - YARD 形式と `NAME [Type] 説明…` の簡潔記法を同時サポート
161
172
  - 引数はデフォルトで安全なリテラルとして解釈し、必要に応じて厳格 JSON モードや Ruby eval モードを切り替え可能
162
173
  - `--pre-script`(エイリアス: `--init`)で任意の Ruby コードを評価し、その結果オブジェクトを公開
163
- - `RUBYCLI_STRICT=ON` で有効化できる厳格モードにより、コメントとシグネチャの矛盾を警告として検知可能
174
+ - `--check` でコメント整合性を lint、`--strict` で入力値をドキュメント通りに強制する二段構えのガード
164
175
 
165
176
  ## Python Fire との違い
166
177
 
167
178
  - **コメント対応のヘルプ生成**: コメントがあればヘルプに反映しつつ、最終的な判断は常にライブなメソッド定義に基づきます。
168
179
  - **型に基づく解析**: `NAME [String]` や YARD タグから型を推論し、真偽値・配列・数値などを自動変換します。
169
- - **厳密な整合性チェック**: 厳格モードを有効にすれば、コメントとメソッド定義が食い違う際に警告を出して保守性を高められます。
180
+ - **厳密な整合性チェック**: `rubycli --check` でコメントと実装のズレを実行前に検査し、通常実行時に `--strict` を付ければドキュメントで宣言した型・許可値以外の入力を拒否できます。
170
181
  - **Ruby 向け拡張**: キーワード引数やブロック (`@yield*`) といった Ruby 固有の構文に合わせたパーサや `RUBYCLI_*` 環境変数を用意しています。
171
182
 
172
183
  | 機能 | Python Fire | Rubycli |
173
184
  | ---- | ----------- | -------- |
174
185
  | 属性の辿り方 | オブジェクトを辿ってプロパティ/属性を自動公開 | 対象オブジェクトの公開メソッドをそのまま公開(暗黙の辿りは無し) |
175
- | クラス初期化 | `__init__` 引数を CLI で自動受け取りインスタンス化 | `--new` を指定した明示的な初期化のみ。引数はコメントで宣言 |
186
+ | クラス初期化 | `__init__` 引数を CLI で自動受け取りインスタンス化 | `--new` 指定時だけ引数なしで明示的に初期化(初期化引数の CLI 受け渡しは未対応なので、必要なら pre-script や自前ファクトリで注入) |
176
187
  | インタラクティブシェル | コマンド未指定時に Fire REPL を提供 | インタラクティブモード無し。コマンド実行専用 |
177
188
  | 情報源 | 反射で引数・プロパティを解析 | ライブなメソッド定義を基点にしつつコメントをヘルプへ反映 |
178
189
  | 辞書/配列 | dict/list を自動でサブコマンド化 | クラス/モジュールのメソッドに特化(辞書自動展開なし) |
@@ -271,15 +282,62 @@ README のサンプルは既定スタイルとして大文字プレースホル
271
282
 
272
283
  > 補足: コメント内で任意引数を角括弧で表す必要はありません。Ruby 側のメソッドシグネチャから必須/任意は自動判定され、ヘルプ出力では Rubycli が適切に角括弧を追加します。
273
284
 
274
- 型ヒントは `[String]`, `(String)`, `(type: String)` のように角括弧・丸括弧・`type:` プレフィックスのいずれでも指定できます。複数型は `(String, nil)` `(type: String, nil)` のように列挙してください。
285
+ 型ヒントは `[String]` `(String)` のように角括弧/丸括弧で指定できます。複数型は `(String, nil)` のように列挙してください。
286
+
287
+ `VALUE...` のような繰り返し指定(`TAG...` など)や、`[String[]]` / `Array<String>` といった配列型の注釈が付いたオプションは配列として扱われます。JSON/YAML 形式のリスト(例: `--tags '["build","test"]'`)を渡すか、カンマ区切り文字列(`--tags "build,test"`)を渡すことで配列に変換されます。スペース区切りの複数値入力(`--tags build test`)にはまだ対応しておらず、繰り返し注記のないオプションは従来どおりスカラーとして扱われます。`--strict` 実行時は各要素の型も検証されるため、`[String[]]` と書かれているのに `--tags [1,2]` のような数値配列を渡すと即エラーになります。
275
288
 
276
- `VALUE...` のような繰り返し指定(`TAG...` など)や、`[String[]]` / `Array<String>` といった配列型の注釈が付いたオプションは配列として扱われます。JSON/YAML 形式のリスト(例: `--tags '["build","test"]'`)を渡すか、カンマ区切り文字列(`--tags "build,test"`)を渡すことで配列に変換されます。スペース区切りの複数値入力(`--tags build test`)にはまだ対応しておらず、繰り返し注記のないオプションは従来どおりスカラーとして扱われます。
289
+ JSON やカンマ区切りで表現しづらいシンボル配列・ハッシュなどを渡したい場合は eval モード(`--eval-args`/`-e` または `--eval-lax`/`-E`)を有効にし、ドキュメントで宣言した型に合わせた Ruby リテラルを渡してください。スペース区切りが未対応でも、安全に複数選択を指定できます(後述の eval 例を参照)。
277
290
 
278
291
  代表的な推論例:
279
292
 
280
293
  - `ARG1` のように型ラベルを省略したプレースホルダは既定で `String` として扱われます。
281
294
  - `--name ARG1` のようにオプションへプレースホルダだけを指定しても同じく `String` が推論されます。
282
295
  - `--verbose` のように値プレースホルダを省略したオプションは Boolean フラグとして扱われます。
296
+ - 位置引数を Boolean にしたい場合は必ず `[Boolean]` を明示してください。`NAME 説明` や `@param name 説明` のように型を省略すると、Ruby 側のデフォルト値に関わらず `String` とみなされます。
297
+
298
+ ### リテラル列挙による制約
299
+
300
+ `--format MODE [:json, :yaml, :auto]` や `LEVEL [:info, :warn]` のように型注釈内へ許容リテラルを列挙すると、ヘルプに選択肢を表示しつつ Rubycli が入力制約として解釈します。シンボル・文字列(裸の単語も可)・真偽値・数値・`nil` に対応し、型ヒントと混在させて `--channel TARGET [:stdout, :stderr, Boolean]` のような宣言も書けます。`%i[info warn]` / `%w[debug info]` などの短縮記法も展開されるため、`LEVEL %i[info warn]` でも同じ効果になります。通常実行では許可外の入力に警告を表示して続行し、`--strict` を付けた場合は `Rubycli::ArgumentError` を送出して即座に停止します。
301
+
302
+ > シンボルと文字列は厳密に区別されます。`[:info, :warn]` と書いた場合は `:info` のようにコロン付きで入力してください。`["info", "warn"]` を選んだ場合はプレーンな文字列のみ受け付けます。
303
+
304
+ > 列挙は各スカラー値に適用されます。`[Symbol[]]` のような配列注釈に対して「許可される組み合わせ」をリテラルで書く構文(例: `[%i[foo bar][]]`)は未サポートなので、必要に応じて文章で説明するか、eval モードで Ruby の配列を渡してください。
305
+
306
+ ```bash
307
+ # literal choice デモ (examples/strict_choices_demo.rb)
308
+ ruby examples/strict_choices_demo.rb report warn --format json
309
+ #=> [WARN] format=json
310
+
311
+ # --strict を付けると仕様外の値で即エラー
312
+ ruby -Ilib exe/rubycli --strict examples/strict_choices_demo.rb report debug
313
+ #=> Rubycli::ArgumentError: Value "debug" for LEVEL is not allowed: allowed values are :info, :warn, :error
314
+ ```
315
+
316
+ ```bash
317
+ # シンボル入力はコロンを付ける
318
+ ruby -Ilib exe/rubycli --strict examples/strict_choices_demo.rb report :warn
319
+ #=> [WARN] format=text
320
+
321
+ ruby -Ilib exe/rubycli --strict examples/strict_choices_demo.rb report warn
322
+ #=> Rubycli::ArgumentError: Value "warn" for LEVEL is not allowed: allowed values are :info, :warn, :error
323
+ ```
324
+
325
+ ### 標準ライブラリ型ヒント
326
+
327
+ コメントに `Date` や `Time`, `BigDecimal`, `Pathname` など標準ライブラリの型名を書けば、Rubycli が必要な `require` を行った上で CLI 引数をその型へ変換します。
328
+
329
+ ```bash
330
+ # examples/typed_arguments_demo.rb より
331
+ ruby examples/typed_arguments_demo.rb ingest \
332
+ --date 2024-12-25 \
333
+ --moment 2024-12-25T10:00:00Z \
334
+ --budget 123.45 \
335
+ --input ./data/input.csv
336
+ ```
337
+
338
+ ハンドラ側には `Date` / `Time` / `BigDecimal` / `Pathname` のインスタンスがそのまま渡るため、追加のパース処理は不要です。
339
+
340
+ 各オプションには既定値があるため、`ruby examples/typed_arguments_demo.rb ingest --budget 999.99` のように個別の型だけ試すこともできます。
283
341
 
284
342
  `@example` や `@raise`, `@see`, `@deprecated` などその他の YARD タグは、現状ヘルプ出力には反映されません。
285
343
 
@@ -318,7 +376,6 @@ Available commands:
318
376
  scale AMOUNT [<FACTOR>] [--clamp=<value>] [--notify]
319
377
 
320
378
  Detailed command help: fallback_example.rb COMMAND help
321
- Enable debug logging: --debug or RUBYCLI_DEBUG=true
322
379
  ```
323
380
 
324
381
  ```bash
@@ -337,7 +394,7 @@ Options:
337
394
  --notify [Boolean] optional (default: false)
338
395
  ```
339
396
 
340
- `AMOUNT` だけがドキュメント化されていますが、`factor` や `clamp`, `notify` も自動的に補完され、既定値や型が推論されていることがわかります。コメントとシグネチャの矛盾を早期に検知したい場合は `RUBYCLI_STRICT=ON` で厳格モードを有効化してください。
397
+ `AMOUNT` だけがドキュメント化されていますが、`factor` や `clamp`, `notify` も自動的に補完され、既定値や型が推論されていることがわかります。開発時は `rubycli --check 対象.rb` でコメントとシグネチャの矛盾を検出し、本番実行で `--strict` を付ければ仕様外の入力をその場で弾けます。
341
398
 
342
399
  #### 存在しない引数やオプションをコメントに書いた場合
343
400
 
@@ -374,7 +431,17 @@ YAML 固有の書き方は拒否され、無効な JSON であれば `JSON::Pars
374
431
 
375
432
  `--eval-args`(短縮形 `-e`)を使うと、後続の引数を Ruby コードとして評価した結果を CLI に渡せます。JSON や YAML では表現しづらいオブジェクトを扱いたいときに便利ですが、評価は `Object.new.instance_eval { binding }` 上で行われるため、信頼できる入力に限定してください。コード内では `Rubycli.with_eval_mode(true) { … }` で切り替えられます。
376
433
 
377
- `--eval-args`/`-e` と `--json-args`/`-j` は同時指定できません。どちらのモードも既定のリテラル解析を拡張する位置づけなので、用途に応じて厳格な JSON か Ruby eval のどちらかを選択してください。
434
+ Ruby 評価はシンボルや配列/ハッシュもそのまま扱えるため、列挙値の組み合わせをオプションへ渡すときにも役立ちます。
435
+
436
+ ```bash
437
+ rubycli -E scripts/report_runner.rb publish \
438
+ --targets '[:marketing, :sales]' \
439
+ --channels '[:email, :slack]'
440
+ ```
441
+
442
+ Ruby 評価を使いつつ、構文エラーが出たときは元の文字列にフォールバックさせたい場合は `--eval-lax`(短縮形 `-E`)を指定します。`--eval-args` と同じく eval モードを有効にしますが、Ruby として解釈できなかったトークン(例: 素の `https://example.com`)は警告を出した上でそのまま渡すため、`60*60*24*14` のような式と文字列を気軽に混在させられます。
443
+
444
+ `--json-args`/`-j` は `--eval-args`/`-e` および `--eval-lax`/`-E` と同時指定できません。どのモードも既定のリテラル解析を拡張する位置づけなので、用途に応じて厳格な JSON か Ruby eval(通常/lax)のいずれかを選択してください。
378
445
 
379
446
  ## Pre-script ブートストラップ
380
447
 
@@ -412,8 +479,9 @@ rubycli --pre-script scripts/bootstrap_runner.rb \
412
479
 
413
480
  | 変数 / フラグ | 説明 | 既定値 |
414
481
  | ------------- | ---- | ------ |
415
- | `--debug` / `RUBYCLI_DEBUG=true` | デバッグログ表示 | `false` |
416
- | `RUBYCLI_STRICT=ON` | 厳格モードを有効化(コメントとシグネチャの矛盾を警告) | `OFF` |
482
+ | `RUBYCLI_DEBUG=true` | デバッグログ表示 | `false` |
483
+ | `--check` | コメント/実装のズレを検査し、コマンドは実行しない | `off` |
484
+ | `--strict` | ドキュメントで許可した型・値以外をエラーとして拒否 | `off` |
417
485
  | `RUBYCLI_ALLOW_PARAM_COMMENT=OFF` | レガシーな `@param` 記法を無効化(互換性のため既定では ON) | `ON` |
418
486
 
419
487
  ## Rubycli API
data/README.md CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  ![Rubycli logo](assets/rubycli-logo.png)
4
4
 
5
- Rubycli turns existing Ruby classes and modules into CLIs by reading their documentation comments. It is inspired by [Python Fire](https://github.com/google/python-fire) but is not a drop-in port or an official project; the focus here is Ruby’s documentation conventions and type annotations.
5
+ Rubycli turns existing Ruby classes and modules into CLIs by inspecting their public method definitions and the doc comments attached to those methods. It is inspired by [Python Fire](https://github.com/google/python-fire) but is not a drop-in port or an official project; the focus here is Ruby’s documentation conventions and type annotations, and those annotations can actively change how a CLI argument is coerced (for example, `TAG... [String[]]` forces array parsing).
6
6
 
7
7
  > 🇯🇵 Japanese documentation is available in [README.ja.md](README.ja.md).
8
8
 
@@ -35,7 +35,6 @@ Available commands:
35
35
  greet <NAME>
36
36
 
37
37
  Detailed command help: hello_app.rb COMMAND help
38
- Enable debug logging: --debug or RUBYCLI_DEBUG=true
39
38
  ```
40
39
 
41
40
  ```bash
@@ -109,7 +108,6 @@ Available commands:
109
108
  greet <NAME> [--shout]
110
109
 
111
110
  Detailed command help: hello_app_with_docs.rb COMMAND help
112
- Enable debug logging: --debug or RUBYCLI_DEBUG=true
113
111
  ```
114
112
 
115
113
  ```bash
@@ -154,11 +152,24 @@ ruby examples/hello_app_with_require.rb greet Hanako --shout
154
152
  #=> HELLO, HANAKO!
155
153
  ```
156
154
 
155
+ ## Constant resolution modes
156
+
157
+ Rubycli assumes that the file name (CamelCased) matches the class or module you want to expose. When that is not the case you can choose how eagerly Rubycli should pick a constant:
158
+
159
+ | Mode | How to enable | Behaviour |
160
+ | --- | --- | --- |
161
+ | `strict` (default) | do nothing / `RUBYCLI_AUTO_TARGET=strict` | Fails unless the CamelCase name matches. The error lists the detected constants and gives explicit rerun instructions. |
162
+ | `auto` | `--auto-target`, `-a`, or `RUBYCLI_AUTO_TARGET=auto` | If exactly one constant in that file defines CLI-callable methods, Rubycli auto-selects it; otherwise you still get the friendly error message. |
163
+
164
+ This keeps large projects safe by default but still provides a one-flag escape hatch when you prefer the fully automatic behaviour.
165
+
166
+ > **Instance-only classes** – If a class only defines public *instance* methods (for example, it exposes functionality via `def greet` on the instance), you must run Rubycli with `--new` so the class is instantiated before commands are resolved. Otherwise Rubycli cannot see any CLI-callable methods. Add at least one public class method when you do not want to rely on `--new`.
167
+
157
168
  ## Project Philosophy
158
169
 
159
170
  - **Convenience first** – The goal is to wrap existing Ruby scripts in a CLI with almost no manual plumbing. Fidelity with Python Fire is not a requirement.
160
171
  - **Inspired, not a port** – We borrow ideas from Python Fire, but we do not aim for feature parity. Missing Fire features are generally “by design.”
161
- - **Comments enrich, code decides** – Method signatures remain the source of truth; optional documentation comments add richer help output and can surface warnings when you opt into strict mode (`RUBYCLI_STRICT=ON`).
172
+ - **Method definitions first, comments augment behavior** – Public method signatures determine what gets exposed (and which arguments are required), while doc comments like `TAG...` or `[Integer]` can turn the very same CLI value into arrays, integers, booleans, etc. Rubycli also auto-parses inputs that look like JSON/YAML literals (for example `--names='["Alice","Bob"]'`) before enforcing the documented type. Run `rubycli --check path/to/script.rb` to lint documentation mismatches, and pass `--strict` during normal runs when you want invalid input to abort instead of merely warning. (Current limitation: `--check` verifies that comments cover every argument, but it does not yet validate the spelling of type tokens—`Booalean` will be treated as a plain string.)
162
173
  - **Lightweight maintenance** – Much of the implementation was generated with AI assistance; contributions that diverge into deep Ruby metaprogramming are out of scope. Please discuss expectations before opening parity PRs.
163
174
 
164
175
  ## Features
@@ -167,19 +178,19 @@ ruby examples/hello_app_with_require.rb greet Hanako --shout
167
178
  - Automatic option signature inference (`NAME [Type] Description…`) without extra DSLs
168
179
  - Safe literal parsing out of the box (arrays / hashes / booleans) with opt-in strict JSON and Ruby eval modes
169
180
  - Optional pre-script hook (`--pre-script` / `--init`) to evaluate Ruby and expose the resulting object
170
- - Opt-in strict mode (`RUBYCLI_STRICT=ON`) that emits warnings whenever comments contradict method signatures
181
+ - Dedicated CLI flags for quality gates: `--check` lints documentation/comments without running commands, and `--strict` treats documented types/choices as hard requirements
171
182
 
172
183
  ## How it differs from Python Fire
173
184
 
174
185
  - **Comment-aware help** – Rubycli leans on doc comments when present but still reflects the live method signature, keeping code as the ultimate authority.
175
186
  - **Type-aware parsing** – Placeholder syntax (`NAME [String]`) and YARD tags let Rubycli coerce arguments to booleans, arrays, numerics, etc. without additional code.
176
- - **Strict validation** – Opt-in strict mode surfaces warnings when comments fall out of sync with method signatures, helping teams keep help text accurate.
187
+ - **Strict validation** – `rubycli --check` lint runs catch documentation drift before execution, while runtime `--strict` runs turn documented types/choices into enforceable contracts.
177
188
  - **Ruby-centric tooling** – Supports Ruby-specific conventions such as optional keyword arguments, block documentation (`@yield*` tags), and `RUBYCLI_*` environment toggles.
178
189
 
179
190
  | Capability | Python Fire | Rubycli |
180
191
  | ---------- | ----------- | -------- |
181
192
  | Attribute traversal | Recursively exposes attributes/properties on demand | Exposes public methods defined on the target; no implicit traversal |
182
- | Constructor handling | Automatically prompts for `__init__` args when instantiating classes | Requires explicit `--new` plus comment docs; no automatic prompting |
193
+ | Constructor handling | Automatically prompts for `__init__` args when instantiating classes | `--new` simply instantiates without passing CLI args (use pre-scripts or your own factories if you need injected dependencies) |
183
194
  | Interactive shell | Offers Fire-specific REPL when invoked without command | No interactive shell mode; strictly command execution |
184
195
  | Input discovery | Pure reflection, no doc comments required | Doc comments drive option names, placeholders, and validation |
185
196
  | Data structures | Dictionaries / lists become subcommands by default | Focused on class or module methods; no automatic dict/list expansion |
@@ -280,15 +291,62 @@ The CLI treats `--flag VALUE`, `--flag <value>`, and `--flag=<value>` identicall
280
291
 
281
292
  > Tip: You do not need to wrap optional arguments in brackets inside the comment. Rubycli already knows which parameters are optional from the Ruby signature and will introduce the brackets in generated help.
282
293
 
283
- You can annotate types using `[String]`, `(String)`, or `(type: String)`—they all convey the same hint, and you can list multiple types such as `(String, nil)` or `(type: String, nil)`.
294
+ You can annotate types using `[String]` or `(String)`—they both convey the same hint, and you can list multiple types such as `(String, nil)`.
295
+
296
+ Repeated values (`VALUE...`) now materialize as arrays automatically whenever the option is documented with an ellipsis (for example `TAG...`) or an explicit array type hint (`[String[]]`, `Array<String>`). Supply either JSON/YAML list syntax (`--tags "[\"build\",\"test\"]"`) or a comma-delimited string (`--tags "build,test"`); Rubycli will coerce both forms to arrays. Space-separated multi-value flags (`--tags build test`) are still not supported, and options without a repeated/array hint continue to be parsed as scalars. Strict mode still verifies each element against the documented type, so `--tags [1,2]` will fail when the docs say `[String[]]`.
284
297
 
285
- Repeated values (`VALUE...`) now materialize as arrays automatically whenever the option is documented with an ellipsis (for example `TAG...`) or an explicit array type hint (`[String[]]`, `Array<String>`). Supply either JSON/YAML list syntax (`--tags "[\"build\",\"test\"]"`) or a comma-delimited string (`--tags "build,test"`); Rubycli will coerce both forms to arrays. Space-separated multi-value flags (`--tags build test`) are still not supported, and options without a repeated/array hint continue to be parsed as scalars.
298
+ Need to pass structures that are awkward to express as JSON (for example symbol arrays or hashes)? Enable eval mode (`--eval-args`/`-e` or `--eval-lax`/`-E`) and supply a Ruby literal that matches the documented type; the example in the eval section below shows how to pass multiple enum selections safely even though space-separated syntax remains unsupported.
286
299
 
287
300
  Common inference rules:
288
301
 
289
302
  - Writing a placeholder such as `ARG1` (without `[String]`) makes Rubycli treat it as a `String`.
290
303
  - Using that placeholder in an option line (`--name ARG1`) also infers a `String`.
291
304
  - Omitting the placeholder entirely (`--verbose`) produces a Boolean flag.
305
+ - Positional arguments only become booleans when you annotate `[Boolean]`; a bare `NAME Description` (or `@param name Description`) falls back to `String`, regardless of the Ruby default value.
306
+
307
+ ### Literal choices and enums
308
+
309
+ You can express a finite set of accepted values directly inside the type annotation, for example `--format MODE [:json, :yaml, :auto]` or `LEVEL [:info, :warn]`. Symbols, strings (including barewords), booleans, numbers, and `nil` are supported, and you can mix literal entries with broader types such as `--channel TARGET [:stdout, :stderr, Boolean]`. `%i[info warn]` / `%w[debug info]` short-hands expand as expected, so `LEVEL %i[info warn]` works the same as the explicit array form. Rubycli always records these choices in the generated help; when you run with `--strict`, any value outside the documented set results in `Rubycli::ArgumentError`, otherwise a warning is printed and execution proceeds.
310
+
311
+ > Symbols and strings are compared strictly. `[:info, :warn]` requires symbol inputs such as `:info`, while `["info", "warn"]` only accepts plain strings. Prefix a value with `:` at the CLI to pass a symbol.
312
+
313
+ > Literal enums currently apply to each scalar argument. If an option is documented as an array (for example `[Symbol[]]`), spell out the allowed members in prose for now—combined literal arrays such as `[%i[foo bar][]]` are not supported.
314
+
315
+ ```bash
316
+ # literal choices + booleans (see examples/strict_choices_demo.rb)
317
+ ruby examples/strict_choices_demo.rb report warn --format json
318
+ #=> [WARN] format=json
319
+
320
+ # the same command with --strict will abort when values drift
321
+ ruby -Ilib exe/rubycli --strict examples/strict_choices_demo.rb report debug
322
+ #=> Rubycli::ArgumentError: Value "debug" for LEVEL is not allowed: allowed values are :info, :warn, :error
323
+ ```
324
+
325
+ ```bash
326
+ # symbol values stay distinct
327
+ ruby -Ilib exe/rubycli --strict examples/strict_choices_demo.rb report :warn
328
+ #=> [WARN] format=text
329
+
330
+ ruby -Ilib exe/rubycli --strict examples/strict_choices_demo.rb report warn
331
+ #=> Rubycli::ArgumentError: Value "warn" for LEVEL is not allowed: allowed values are :info, :warn, :error
332
+ ```
333
+
334
+ ### Standard library type hints
335
+
336
+ Doc comments can reference standard classes such as `Date`, `Time`, `BigDecimal`, or `Pathname`. Rubycli loads the necessary stdlib files on demand and coerces CLI inputs using the documented types.
337
+
338
+ ```bash
339
+ # see examples/typed_arguments_demo.rb
340
+ ruby examples/typed_arguments_demo.rb ingest \
341
+ --date 2024-12-25 \
342
+ --moment 2024-12-25T10:00:00Z \
343
+ --budget 123.45 \
344
+ --input ./data/input.csv
345
+ ```
346
+
347
+ This command prints a normalized summary and the handler receives real `Date`, `Time`, `BigDecimal`, and `Pathname` objects without manual parsing.
348
+
349
+ Each option has a sensible default, so you can also experiment one at a time (for example `ruby examples/typed_arguments_demo.rb ingest --budget 999.99`).
292
350
 
293
351
  Other YARD tags such as `@example`, `@raise`, `@see`, and `@deprecated` are currently ignored by the CLI renderer.
294
352
 
@@ -327,7 +385,6 @@ Available commands:
327
385
  scale AMOUNT [<FACTOR>] [--clamp=<value>] [--notify]
328
386
 
329
387
  Detailed command help: fallback_example.rb COMMAND help
330
- Enable debug logging: --debug or RUBYCLI_DEBUG=true
331
388
  ```
332
389
 
333
390
  ```bash
@@ -346,7 +403,7 @@ Options:
346
403
  --notify [Boolean] optional (default: false)
347
404
  ```
348
405
 
349
- Here only `AMOUNT` is documented, yet `factor`, `clamp`, and `notify` are still presented with sensible defaults and inferred types. Enable strict mode (`RUBYCLI_STRICT=ON`) if you want mismatches between comments and signatures to surface as warnings during development.
406
+ Here only `AMOUNT` is documented, yet `factor`, `clamp`, and `notify` are still presented with sensible defaults and inferred types. Run `rubycli --check path/to/script.rb` during development to surface mismatches between comments and signatures, and pass `--strict` when executing commands to enforce the documented types/choices.
350
407
 
351
408
  #### What if the docs mention arguments that do not exist?
352
409
 
@@ -382,7 +439,17 @@ rubycli -e scripts/data_cli.rb DataCLI run '(1..10).to_a'
382
439
 
383
440
  Under the hood Rubycli evaluates each argument inside an isolated binding (`Object.new.instance_eval { binding }`). Treat this as unsafe input: do not enable it for untrusted callers. The mode can also be toggled programmatically via `Rubycli.with_eval_mode(true) { … }`.
384
441
 
385
- `--eval-args`/`-e` and `--json-args`/`-j` are mutually exclusive; Rubycli will raise an error if both are present. Both modes augment the default literal parsing, so you can pick either strict JSON or full Ruby evaluation when the defaults are not enough.
442
+ Because Ruby evaluation understands symbols, arrays, and hashes, it’s a convenient way to pass literal enum combinations to options that expect arrays:
443
+
444
+ ```bash
445
+ rubycli -E scripts/report_runner.rb publish \
446
+ --targets '[:marketing, :sales]' \
447
+ --channels '[:email, :slack]'
448
+ ```
449
+
450
+ Need Ruby evaluation plus a safety net? Pass `--eval-lax` (or `-E`). It flips on eval mode just like `--eval-args`, but if Ruby fails to parse a token (for example, a bare `https://example.com`), Rubycli emits a warning and forwards the original string unchanged. This lets you mix inline math (`60*60*24*14`) with literal values without constantly juggling quotes.
451
+
452
+ `--json-args`/`-j` cannot be combined with either `--eval-args`/`-e` or `--eval-lax`/`-E`; Rubycli will raise an error if both are present. Both modes augment the default literal parsing, so you can pick either strict JSON or one of the Ruby eval variants when the defaults are not enough.
386
453
 
387
454
  ## Pre-script bootstrap
388
455
 
@@ -420,8 +487,9 @@ This keeps `--new` available for quick zero-argument instantiation while allowin
420
487
 
421
488
  | Flag / Env | Description | Default |
422
489
  | ---------- | ----------- | ------- |
423
- | `--debug` / `RUBYCLI_DEBUG=true` | Print debug logs | `false` |
424
- | `RUBYCLI_STRICT=ON` | Enable strict mode validation (prints warnings on comment/signature drift) | `OFF` |
490
+ | `RUBYCLI_DEBUG=true` | Print debug logs | `false` |
491
+ | `--check` | Validate documentation/comments without executing commands | `off` |
492
+ | `--strict` | Enforce documented choices/types; invalid input aborts | `off` |
425
493
  | `RUBYCLI_ALLOW_PARAM_COMMENT=OFF` | Disable legacy `@param` lines (defaults to on today for compatibility) | `ON` |
426
494
 
427
495
  ## Library helpers
@@ -0,0 +1,69 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Rubycli
4
+ # Coordinates json/eval argument modes and enforces mutual exclusion.
5
+ class ArgumentModeController
6
+ def initialize(json_coercer:, eval_coercer:)
7
+ @json_coercer = json_coercer
8
+ @eval_coercer = eval_coercer
9
+ end
10
+
11
+ def json_mode?
12
+ @json_coercer.json_mode?
13
+ end
14
+
15
+ def eval_mode?
16
+ @eval_coercer.eval_mode?
17
+ end
18
+
19
+ def with_json_mode(enabled = true, &block)
20
+ enforce_mutual_exclusion!(:json, enabled)
21
+ @json_coercer.with_json_mode(enabled, &block)
22
+ end
23
+
24
+ def with_eval_mode(enabled = true, **options, &block)
25
+ enforce_mutual_exclusion!(:eval, enabled)
26
+ @eval_coercer.with_eval_mode(enabled, **options, &block)
27
+ end
28
+
29
+ def apply_argument_coercions(positional_args, keyword_args)
30
+ ensure_modes_compatible!
31
+
32
+ if json_mode?
33
+ coerce_values!(positional_args, keyword_args) { |value| @json_coercer.coerce_json_value(value) }
34
+ end
35
+
36
+ if eval_mode?
37
+ coerce_values!(positional_args, keyword_args) { |value| @eval_coercer.coerce_eval_value(value) }
38
+ end
39
+ rescue ::ArgumentError => e
40
+ raise Rubycli::ArgumentError, e.message
41
+ end
42
+
43
+ private
44
+
45
+ def enforce_mutual_exclusion!(mode, enabled)
46
+ return unless enabled
47
+
48
+ case mode
49
+ when :json
50
+ raise Rubycli::ArgumentError, '--json-args cannot be combined with --eval-args or --eval-lax' if eval_mode?
51
+ when :eval
52
+ raise Rubycli::ArgumentError, '--json-args cannot be combined with --eval-args or --eval-lax' if json_mode?
53
+ end
54
+ end
55
+
56
+ def ensure_modes_compatible!
57
+ if json_mode? && eval_mode?
58
+ raise Rubycli::ArgumentError, '--json-args cannot be combined with --eval-args or --eval-lax'
59
+ end
60
+ end
61
+
62
+ def coerce_values!(positional_args, keyword_args)
63
+ positional_args.map! { |value| yield(value) }
64
+ keyword_args.keys.each do |key|
65
+ keyword_args[key] = yield(keyword_args[key])
66
+ end
67
+ end
68
+ end
69
+ end