rubycli 0.1.7 → 0.2.0

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.
data/README.md CHANGED
@@ -1,14 +1,52 @@
1
1
  # Rubycli — Python Fire-inspired CLI for Ruby
2
2
 
3
- ![Rubycli logo](assets/rubycli-logo.png)
3
+ ![Rubycli logo](https://raw.githubusercontent.com/inakaegg/rubycli/main/assets/rubycli-logo.png)
4
4
 
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).
5
+ [![Gem Version](https://img.shields.io/gem/v/rubycli)](https://rubygems.org/gems/rubycli)
6
6
 
7
- > 🇯🇵 Japanese documentation is available in [README.ja.md](README.ja.md).
7
+ Rubycli turns existing Ruby classes and modules into command-line interfaces.
8
+ It inspects public method definitions and the doc comments attached to them, so
9
+ in the simplest case your script needs no changes at all — not even
10
+ `require "rubycli"`. Type annotations in comments are not just documentation:
11
+ they drive how CLI arguments are parsed (for example, `TAG... [String[]]`
12
+ forces array parsing).
8
13
 
9
- ![Rubycli demo showing generated commands and invocation](assets/rubycli-demo.gif)
14
+ Rubycli is inspired by [Python Fire](https://github.com/google/python-fire) but
15
+ is not a port or an official project; the focus is Ruby's documentation
16
+ conventions and type annotations.
10
17
 
11
- ### 1. Existing Ruby script (Rubycli unaware)
18
+ > 🇯🇵 Japanese documentation: [README.ja.md](README.ja.md)
19
+
20
+ ![Rubycli demo showing generated commands and invocation](https://raw.githubusercontent.com/inakaegg/rubycli/main/assets/rubycli-demo.gif)
21
+
22
+ ## Project status
23
+
24
+ Rubycli is **no longer maintained.** 0.2.0 is the final release and closes the
25
+ project out with the fixes from a full behavioural audit of the released gem
26
+ (eight reproducible defects; see [CHANGELOG.md](CHANGELOG.md)). Issues and pull
27
+ requests are not reviewed, and no further releases are planned.
28
+
29
+ The code stays online as a reference. If you are picking a CLI library for
30
+ production work, use [Thor](https://github.com/rails/thor),
31
+ [dry-cli](https://dry-rb.org/gems/dry-cli/), or Ruby's built-in
32
+ `OptionParser`.
33
+
34
+ ## Installation
35
+
36
+ ```bash
37
+ gem install rubycli
38
+ ```
39
+
40
+ ```ruby
41
+ # Gemfile
42
+ gem "rubycli"
43
+ ```
44
+
45
+ Requires Ruby 3.0 or later. Licensed under [MIT](LICENSE).
46
+
47
+ ## Quick start
48
+
49
+ ### 1. Run an existing script as-is
12
50
 
13
51
  ```ruby
14
52
  # hello_app.rb
@@ -21,7 +59,8 @@ module HelloApp
21
59
  end
22
60
  ```
23
61
 
24
- > Try it yourself: this repository ships with `examples/hello_app.rb`, so from the project root you can run `rubycli examples/hello_app.rb` to explore the generated commands.
62
+ This repository ships the same file as `examples/hello_app.rb`, so you can try
63
+ everything below from the project root.
25
64
 
26
65
  ```bash
27
66
  rubycli examples/hello_app.rb
@@ -32,11 +71,13 @@ Usage: hello_app.rb COMMAND [arguments]
32
71
 
33
72
  Available commands:
34
73
  Class methods:
35
- greet <NAME>
74
+ greet NAME
36
75
 
37
76
  Detailed command help: hello_app.rb COMMAND help
38
77
  ```
39
78
 
79
+ Missing arguments produce a usage message instead of a stack trace:
80
+
40
81
  ```bash
41
82
  rubycli examples/hello_app.rb greet
42
83
  ```
@@ -54,16 +95,16 @@ rubycli examples/hello_app.rb greet Hanako
54
95
  #=> Hello, Hanako!
55
96
  ```
56
97
 
57
- Running `rubycli examples/hello_app.rb --help` prints the same summary as invoking it without a command.
98
+ `rubycli examples/hello_app.rb --help` prints the same summary as invoking it
99
+ without a command.
58
100
 
59
- ### 2. Add documentation hints for richer flags
101
+ ### 2. Add doc comments for typed options
60
102
 
61
- > Still no `require "rubycli"` needed; comments alone drive option parsing and help text.
62
-
63
- **Concise placeholder style**
103
+ Still no `require "rubycli"` needed; comments alone drive option parsing and
104
+ help text. Both the concise placeholder style and YARD-style tags work:
64
105
 
65
106
  ```ruby
66
- # hello_app.rb
107
+ # Concise placeholder style
67
108
  module HelloApp
68
109
  module_function
69
110
 
@@ -77,10 +118,8 @@ module HelloApp
77
118
  end
78
119
  ```
79
120
 
80
- **YARD-style tags work too**
81
-
82
121
  ```ruby
83
- # hello_app.rb
122
+ # YARD-style tags
84
123
  module HelloApp
85
124
  module_function
86
125
 
@@ -94,21 +133,13 @@ module HelloApp
94
133
  end
95
134
  ```
96
135
 
97
- > The documented variant lives at `examples/hello_app_with_docs.rb` if you want to follow along locally.
98
-
99
- ```bash
100
- rubycli examples/hello_app_with_docs.rb
101
- ```
102
-
103
- ```text
104
- Usage: hello_app_with_docs.rb COMMAND [arguments]
105
-
106
- Available commands:
107
- Class methods:
108
- greet <NAME> [--shout]
109
-
110
- Detailed command help: hello_app_with_docs.rb COMMAND help
111
- ```
136
+ The documented variant lives at `examples/hello_app_with_docs.rb`. The module is
137
+ still called `HelloApp`, so the file adds `HelloAppWithDocs = HelloApp` at the
138
+ bottom to keep a constant that matches the file name; that is why it runs
139
+ without extra flags. When a file has no matching constant, see
140
+ [Target constant resolution](#target-constant-resolution) below.
141
+ (Rubycli 0.1.7 and earlier do not detect that alias, so add `--auto-target` /
142
+ `-a` on those versions.)
112
143
 
113
144
  ```bash
114
145
  rubycli examples/hello_app_with_docs.rb greet --help
@@ -129,7 +160,12 @@ rubycli examples/hello_app_with_docs.rb greet --shout Hanako
129
160
  #=> HELLO, HANAKO!
130
161
  ```
131
162
 
132
- Need to keep a helper off the CLI? Define it as `private` on the singleton class:
163
+ Rubycli exposes public methods defined directly on the target class or module.
164
+ Methods inherited from a superclass, mixed in with `include`, or generated by
165
+ `attr_accessor` are not turned into commands, so wrap them in the target itself
166
+ when you want them on the CLI.
167
+
168
+ To keep a helper off the CLI, define it as `private` on the singleton class:
133
169
 
134
170
  ```ruby
135
171
  module HelloApp
@@ -143,95 +179,10 @@ module HelloApp
143
179
  end
144
180
  ```
145
181
 
146
- ### 3. (Optional) Embed the runner inside your script
147
-
148
- Prefer to launch via `ruby ...` directly? Require the gem and delegate to `Rubycli.run` (see Quick start below for `examples/hello_app_with_require.rb`).
149
-
150
- ```bash
151
- ruby examples/hello_app_with_require.rb greet Hanako --shout
152
- #=> HELLO, HANAKO!
153
- ```
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`. Passing `--new` also makes those instance methods appear in `rubycli --help` output and allows `rubycli --check --new` to lint their documentation. When your constructor needs arguments, pass them inline with `--new=VALUE` (safe YAML/JSON-like parsing by default; `--json-args` for strict JSON, `--eval-args` / `--eval-lax` for Ruby literals). Any comments on `initialize` are respected for type coercion just like regular CLI methods.
167
-
168
- > Hint: Single values should be passed as `--new=value` so they aren’t mistaken for the next path/command. Space-separated single tokens like `--new 1` may be treated as the following path unless they look obviously structured.
169
-
170
- ## Project Philosophy
171
-
172
- - **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.
173
- - **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.”
174
- - **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—including undefined type labels or enumerated values, with DidYouMean suggestions for `Booalean`-style typos—and pass `--strict` during normal runs when you want invalid input to abort instead of merely warning.
175
- - **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.
176
-
177
- ## Features
178
-
179
- - Comment-aware CLI generation with both YARD-style tags and concise placeholders
180
- - Automatic option signature inference (`NAME [Type] Description…`) without extra DSLs
181
- - Safe literal parsing out of the box (arrays / hashes / booleans) with opt-in strict JSON and Ruby eval modes
182
- - Optional pre-script hook (`--pre-script` / `--init`) to evaluate Ruby and expose the resulting object
183
- - Dedicated CLI flags for quality gates: `--check` lints documentation/comments without running commands, and `--strict` treats documented types/choices as hard requirements
184
- - Example `examples/new_mode_runner.rb` demonstrates instance-only classes with `--new=VALUE` constructor arguments, eval/JSON modes, and a pre-script initialization pattern.
185
-
186
- ### Examples
187
-
188
- - `examples/hello_app.rb` / `examples/hello_app_with_docs.rb`: minimal module-function variants, with and without docs
189
- - `examples/typed_arguments_demo.rb`: stdlib type coercions (Date/Time/BigDecimal/Pathname)
190
- - `examples/strict_choices_demo.rb`: literal enumerations and `--strict`
191
- - `examples/new_mode_runner.rb`: instance-only class initialized via `--new=VALUE` with eval/JSON/pre-script combinations
192
-
193
- > Tip: `--strict` trusts whatever types/choices your comments spell out—if the annotations are misspelled, runtime enforcement has nothing reliable to compare against. Keep `rubycli --check` in CI so documentation typos are caught before production runs that rely on `--strict`.
194
-
195
- ## How it differs from Python Fire
196
-
197
- - **Comment-aware help** – Rubycli leans on doc comments when present but still reflects the live method signature, keeping code as the ultimate authority.
198
- - **Type-aware parsing** – Placeholder syntax (`NAME [String]`) and YARD tags let Rubycli coerce arguments to booleans, arrays, numerics, etc. without additional code.
199
- - **Strict validation** – `rubycli --check` lint runs catch documentation drift (including undefined type labels or enumerated values) without executing commands, while runtime `--strict` runs turn those documented types/choices into enforceable contracts.
200
- - **Ruby-centric tooling** – Supports Ruby-specific conventions such as optional keyword arguments, block documentation (`@yield*` tags), and `RUBYCLI_*` environment toggles.
201
-
202
- | Capability | Python Fire | Rubycli |
203
- | ---------- | ----------- | -------- |
204
- | Attribute traversal | Recursively exposes attributes/properties on demand | Exposes public methods defined on the target; no implicit traversal |
205
- | Constructor handling | Automatically prompts for `__init__` args when instantiating classes | `--new` instantiates and accepts constructor arguments via `--new=VALUE` (safe YAML/JSON-like parsing by default; `--json-args` for strict JSON, `--eval-args` / `--eval-lax` for Ruby literals). Use pre-scripts or your own factories for more complex wiring. |
206
- | Interactive shell | Offers Fire-specific REPL when invoked without command | No interactive shell mode; strictly command execution |
207
- | Input discovery | Pure reflection, no doc comments required | Doc comments drive option names, placeholders, and validation |
208
- | Data structures | Dictionaries / lists become subcommands by default | Focused on class or module methods; no automatic dict/list expansion |
209
-
210
- #### Example commands
211
-
212
- - `rubycli examples/new_mode_runner.rb run --new='["a","b","c"]' --mode reverse`
213
- - `rubycli --json-args --new='["x","y"]' examples/new_mode_runner.rb run --mode summary --options '{"source":"json"}'`
214
- - `rubycli --eval-args --new='["x","y"]' examples/new_mode_runner.rb run --mode summary --options '{tags: [:a, :b]}'`
215
- - `rubycli --pre-script 'NewModeRunner.new(%w[a b c], options: {from: :pre})' examples/new_mode_runner.rb run --mode summary`
216
-
217
- ## Installation
218
-
219
- Rubycli is published on RubyGems.
220
-
221
- ```bash
222
- gem install rubycli
223
- ```
224
-
225
- Bundler example:
226
-
227
- ```ruby
228
- # Gemfile
229
- gem "rubycli"
230
- ```
182
+ ### 3. Optional: embed the runner in your script
231
183
 
232
- ## Quick start (embed Rubycli in the script)
233
-
234
- Step 3 adds `require "rubycli"` so the script can invoke the CLI directly (see `examples/hello_app_with_require.rb`):
184
+ If you prefer launching via plain `ruby`, require the gem and delegate to
185
+ `Rubycli.run` (shipped as `examples/hello_app_with_require.rb`):
235
186
 
236
187
  ```ruby
237
188
  # hello_app_with_require.rb
@@ -254,131 +205,219 @@ end
254
205
  Rubycli.run(HelloApp)
255
206
  ```
256
207
 
257
- Run it:
258
-
259
208
  ```bash
260
- ruby examples/hello_app_with_require.rb greet Taro
261
- #=> Hello, Taro!
262
-
263
209
  ruby examples/hello_app_with_require.rb greet Taro --shout
264
210
  #=> HELLO, TARO!
265
211
  ```
266
212
 
267
- To launch the same file without adding `require "rubycli"`, use the bundled executable:
213
+ When you run a file through the bundled `rubycli` executable instead, return
214
+ values are printed automatically.
268
215
 
269
- ```bash
270
- rubycli path/to/hello_app.rb greet --shout Hanako
271
- ```
216
+ Return values go to stdout; warnings, errors, and the usage text shown after a
217
+ failed invocation go to stderr, so `rubycli app.rb command > result.json` keeps
218
+ the payload clean and the exit status non-zero on failure.
219
+
220
+ ## Target constant resolution
272
221
 
273
- When you omit `CLASS_OR_MODULE`, Rubycli now infers it from the file name and even locates nested constants such as `Module1::Inner::Runner`. Return values are printed by default when you run the bundled CLI.
222
+ Rubycli assumes that the file name (CamelCased) matches the class or module you
223
+ want to expose. When it does not, choose how eagerly Rubycli should pick a
224
+ constant:
274
225
 
275
- Need to target a different constant explicitly? Provide it after the file path:
226
+ | Mode | How to enable | Behaviour |
227
+ | --- | --- | --- |
228
+ | `strict` (default) | nothing / `RUBYCLI_AUTO_TARGET=strict` | Fails unless the CamelCase name matches. The error lists the detected constants and shows how to rerun. |
229
+ | `auto` | `--auto-target` / `-a` / `RUBYCLI_AUTO_TARGET=auto` | If exactly one constant in the file defines CLI-callable methods, it is selected automatically. |
230
+
231
+ You can always name the constant explicitly after the file path — useful when a
232
+ file defines several candidates or a nested constant. Two bundled files
233
+ demonstrate both situations: `examples/multi_constant_runner.rb` defines
234
+ `MultiConstantRunner` plus `HelperRunner`, and
235
+ `examples/mismatched_constant_runner.rb` defines only `FriendlyGreeter`.
236
+
237
+ ```bash
238
+ # pick a non-matching constant explicitly (works in the default strict mode)
239
+ rubycli examples/multi_constant_runner.rb HelperRunner inspect
240
+ #=> Helper invoked # printed by the method itself
241
+ #=> :helper # the return value, printed by rubycli
242
+
243
+ # or let auto mode select the single callable constant
244
+ rubycli -a examples/mismatched_constant_runner.rb greet Hanako --message Hi
245
+ #=> Hi, Hanako! # printed by the method itself
246
+ #=> Hi, Hanako! # the same string returned to rubycli (add --quiet to print it once)
247
+ ```
248
+
249
+ Nested constants such as `Outer::Inner::Runner` are found as well; pass the
250
+ fully qualified name after the file path.
251
+
252
+ ## Instance-only classes and `--new`
253
+
254
+ If a class only defines public *instance* methods, run Rubycli with `--new` so
255
+ the class is instantiated before commands are resolved; otherwise Rubycli sees
256
+ no CLI-callable methods.
257
+
258
+ - `--new` also makes instance methods appear in `--help` output, and lets
259
+ `rubycli --check --new` lint their documentation.
260
+ - When the constructor needs an argument, pass it with `--new=VALUE` **before
261
+ the file path**. The value is parsed as a safe YAML/JSON-like literal, and
262
+ comments on `initialize` drive type coercion just like regular CLI methods.
263
+ - `--new=VALUE` supplies exactly **one** value, bound to the constructor's first
264
+ parameter: `--new='["a","b","c"]'` hands over that array as a single argument.
265
+ Arrays are not spread across several parameters and hashes do not become
266
+ keyword arguments — reach for `--pre-script` when the constructor needs more
267
+ than one argument or keyword arguments.
268
+ - Prefer the `--new=VALUE` form over a space-separated `--new VALUE`, so the
269
+ value is not mistaken for the file path.
270
+ - A `--pre-script` that returns an instance exposes instance methods on its own,
271
+ so it can be used instead of `--new`, not only alongside it.
272
+
273
+ Example (`examples/new_mode_runner.rb`):
276
274
 
277
275
  ```bash
278
- rubycli scripts/multi_runner.rb Admin::Runner list --active
276
+ rubycli --new='["a","b","c"]' examples/new_mode_runner.rb run --mode reverse
277
+ ```
278
+
279
+ ```text
280
+ [
281
+ "c",
282
+ "b",
283
+ "a"
284
+ ]
279
285
  ```
280
286
 
281
- This is useful when a file defines multiple candidates or when you want a nested constant that does not match the file name.
287
+ Return values are rendered as pretty-printed JSON when a command returns a
288
+ structure instead of printing it.
282
289
 
283
290
  ## Comment syntax
284
291
 
285
- Rubycli parses a hybrid format you can stick to familiar YARD tags or use short forms.
292
+ Rubycli parses a hybrid format familiar YARD tags or short forms:
286
293
 
287
294
  | Purpose | YARD-compatible | Rubycli style |
288
295
  | ------- | --------------- | ------------- |
289
296
  | Positional argument | `@param name [Type] Description` | `NAME [Type] Description` |
290
- | Keyword option | Same as above | `--flag -f VALUE [Type] Description` |
297
+ | Keyword option | same | `--flag -f VALUE [Type] Description` |
291
298
  | Return value | `@return [Type] Description` | `=> [Type] Description` |
292
299
 
293
- Short options are optional and order-independent, so the following examples are equivalent in Rubycli’s default style:
300
+ Short options are optional and order-independent; these are equivalent:
294
301
 
295
302
  - `--flag -f VALUE [Type] Description`
296
303
  - `--flag VALUE [Type] Description`
297
304
  - `-f --flag VALUE [Type] Description`
298
305
 
299
- Our examples keep the classic uppercase placeholders (`NAME`, `VALUE`) as the canonical style; the variations below are optional sugar.
306
+ Types can be written as `[String]` or `(String)`, and unions as
307
+ `(String, nil)`.
300
308
 
301
309
  ### Alternate placeholder notations
302
310
 
303
- Rubycli also understands these syntaxes when parsing comments and rendering help:
304
-
305
- - Angle brackets for user input: `--flag <value>` or `NAME [<value>]`
306
- - Inline equals for long options: `--flag=<value>`
307
- - Trailing ellipsis for repeated values: `VALUE...` or `<value>...`
308
-
309
- The CLI treats `--flag VALUE`, `--flag <value>`, and `--flag=<value>` identically at runtime—document with whichever variant your team prefers. Optional placeholders like `[VALUE]` or `[VALUE...]` let Rubycli infer boolean flags, optional values, and list coercion. When you omit the placeholder entirely (for example `--quiet`), Rubycli infers a Boolean flag automatically.
311
+ These are understood both when parsing comments and when rendering help:
310
312
 
311
- > 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.
313
+ - Angle brackets: `--flag <value>`, `NAME [<value>]`
314
+ - Inline equals: `--flag=<value>`
315
+ - Trailing ellipsis for repeated values: `VALUE...`, `<value>...`
312
316
 
313
- You can annotate types using `[String]` or `(String)`—they both convey the same hint, and you can list multiple types such as `(String, nil)`.
317
+ At runtime `--flag VALUE`, `--flag <value>`, and `--flag=<value>` are
318
+ identical — document with whichever variant your team prefers. You do not need
319
+ to bracket optional arguments yourself: Rubycli already knows which parameters
320
+ are optional from the Ruby signature and adds the brackets in generated help.
314
321
 
315
- 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[]]`.
322
+ Inference rules when annotations are partial:
316
323
 
317
- 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.
324
+ - A bare placeholder such as `ARG1` (no type) is treated as `String`.
325
+ - An option with no value placeholder (`--verbose`) becomes a Boolean flag.
326
+ - Positional arguments only become booleans with an explicit `[Boolean]`;
327
+ a bare `NAME Description` falls back to `String` regardless of the Ruby
328
+ default value.
318
329
 
319
- Common inference rules:
330
+ ### Arrays and repeated values
320
331
 
321
- - Writing a placeholder such as `ARG1` (without `[String]`) makes Rubycli treat it as a `String`.
322
- - Using that placeholder in an option line (`--name ARG1`) also infers a `String`.
323
- - Omitting the placeholder entirely (`--verbose`) produces a Boolean flag.
324
- - 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.
332
+ Options documented with an ellipsis (`TAG...`) or an array type
333
+ (`[String[]]`, `Array<String>`) are parsed as arrays. Both JSON/YAML list
334
+ syntax (`--tags '["build","test"]'`) and comma-delimited strings
335
+ (`--tags "build,test"`) are accepted. Space-separated multi-value flags
336
+ (`--tags build test`) are not supported, and options without a repeated/array
337
+ hint stay scalars. `--strict` verifies each element against the documented
338
+ type, so `--tags [1,2]` fails when the docs say `[String[]]`. Quoted elements
339
+ remain strings even when their contents look like other literals, such as
340
+ `--tags '["true","null"]'`.
325
341
 
326
342
  ### Literal choices and enums
327
343
 
328
- 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.
344
+ A finite set of accepted values can be written directly inside the type
345
+ annotation: `--format MODE [:json, :yaml, :auto]` or `LEVEL [:info, :warn]`.
346
+ Symbols, strings (including barewords), booleans, numbers, and `nil` are
347
+ supported; literals can be mixed with broader types
348
+ (`--channel TARGET [:stdout, :stderr, Boolean]`), and `%i[info warn]` /
349
+ `%w[debug info]` shorthands expand as expected. The choices always appear in
350
+ generated help; without `--strict` an out-of-range value only prints a warning,
351
+ with `--strict` it aborts.
329
352
 
330
- > 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.
331
-
332
- > 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.
353
+ Symbols and strings are compared strictly: `[:info, :warn]` requires symbol
354
+ input such as `:info` (prefix the value with `:` at the CLI), while
355
+ `["info", "warn"]` only accepts plain strings.
333
356
 
334
357
  ```bash
335
- # literal choices + booleans (see examples/strict_choices_demo.rb)
336
- ruby examples/strict_choices_demo.rb report warn --format json
337
- #=> [WARN] format=json
338
-
339
- # the same command with --strict will abort when values drift
340
- ruby -Ilib exe/rubycli --strict examples/strict_choices_demo.rb report debug
341
- #=> Rubycli::ArgumentError: Value "debug" for LEVEL is not allowed: allowed values are :info, :warn, :error
342
- ```
358
+ # see examples/strict_choices_demo.rb — LEVEL is documented as [:info, :warn, :error]
359
+ rubycli examples/strict_choices_demo.rb report :warn --format json
360
+ #=> [WARN] format=json (followed by the returned hash)
343
361
 
344
- ```bash
345
- # symbol values stay distinct
346
- ruby -Ilib exe/rubycli --strict examples/strict_choices_demo.rb report :warn
347
- #=> [WARN] format=text
362
+ # a plain string is not the documented symbol: warn and continue
363
+ rubycli examples/strict_choices_demo.rb report warn
364
+ #=> [WARN] LEVEL must be one of :info, :warn, :error (received "warn") (use --strict to abort on invalid input)
348
365
 
349
- ruby -Ilib exe/rubycli --strict examples/strict_choices_demo.rb report warn
350
- #=> Rubycli::ArgumentError: Value "warn" for LEVEL is not allowed: allowed values are :info, :warn, :error
366
+ # with --strict, out-of-range input aborts
367
+ rubycli --strict examples/strict_choices_demo.rb report debug
368
+ #=> [ERROR] LEVEL must be one of :info, :warn, :error (received "debug")
351
369
  ```
352
370
 
371
+ Literal enums currently apply to each scalar argument; combined literal arrays
372
+ such as `[%i[foo bar][]]` are not supported.
373
+
353
374
  ### Standard library type hints
354
375
 
355
- 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.
376
+ Doc comments can reference standard classes such as `Date`, `Time`,
377
+ `BigDecimal`, or `Pathname`. Rubycli loads the required stdlib on demand and
378
+ coerces CLI inputs, so the handler receives real objects without manual
379
+ parsing:
356
380
 
357
381
  ```bash
358
382
  # see examples/typed_arguments_demo.rb
359
- ruby examples/typed_arguments_demo.rb ingest \
383
+ rubycli examples/typed_arguments_demo.rb ingest \
360
384
  --date 2024-12-25 \
361
385
  --moment 2024-12-25T10:00:00Z \
362
386
  --budget 123.45 \
363
387
  --input ./data/input.csv
364
388
  ```
365
389
 
366
- This command prints a normalized summary and the handler receives real `Date`, `Time`, `BigDecimal`, and `Pathname` objects without manual parsing.
390
+ Every option there has a default, so you can also experiment one at a time
391
+ (`... ingest --budget 999.99`).
367
392
 
368
- 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`).
393
+ Other YARD tags such as `@example`, `@raise`, `@see`, and `@deprecated` are
394
+ currently ignored by the help renderer.
369
395
 
370
- Other YARD tags such as `@example`, `@raise`, `@see`, and `@deprecated` are currently ignored by the CLI renderer.
396
+ > To explore every notation in one script, try
397
+ > `rubycli examples/documentation_style_showcase.rb canonical --help` and the
398
+ > other showcase commands.
371
399
 
372
- > Want to explore every notation in a single script? Try `rubycli examples/documentation_style_showcase.rb canonical --help`, `... angled --help`, or the other showcase commands.
400
+ ### Notes on YARD-style comments
373
401
 
374
- YARD-style `@param` annotations continue to work out of the box. If you want to enforce the concise placeholder syntax exclusively, set `RUBYCLI_ALLOW_PARAM_COMMENT=OFF` (strict mode still applies either way).
402
+ - Methods that accept `**kwargs` do not expose those keys automatically; every
403
+ key you want on the CLI needs its own `--long-name PLACEHOLDER [Type] ...`
404
+ line.
405
+ - Bullet lists or free-form lines following a `@param` line are not used for
406
+ CLI generation; put supplementary text in the option's description instead.
407
+ - To migrate towards the concise placeholder syntax, set
408
+ `RUBYCLI_ALLOW_PARAM_COMMENT=OFF` and run `rubycli --check`: every `@param`
409
+ line is then reported as a documentation issue (and ignored for that lint
410
+ run), so you can see what still needs rewriting. It is a linting switch, not
411
+ a runtime restriction — normal runs keep honouring `@param` unchanged, and
412
+ `@return` is never affected.
375
413
 
376
414
  ### When docs are missing or incomplete
377
415
 
378
- Rubycli always trusts the live method signature. If a parameter (or option) is undocumented, the CLI still exposes it using the parameter name and default values inferred from the method definition:
416
+ Rubycli always trusts the live method signature. Undocumented parameters are
417
+ still exposed, with names, defaults, and types inferred from the definition:
379
418
 
380
419
  ```ruby
381
- # fallback_example.rb
420
+ # examples/fallback_example.rb
382
421
  module FallbackExample
383
422
  module_function
384
423
 
@@ -392,20 +431,6 @@ module FallbackExample
392
431
  end
393
432
  ```
394
433
 
395
- ```bash
396
- rubycli examples/fallback_example.rb
397
- ```
398
-
399
- ```text
400
- Usage: fallback_example.rb COMMAND [arguments]
401
-
402
- Available commands:
403
- Class methods:
404
- scale AMOUNT [<FACTOR>] [--clamp=<value>] [--notify]
405
-
406
- Detailed command help: fallback_example.rb COMMAND help
407
- ```
408
-
409
434
  ```bash
410
435
  rubycli examples/fallback_example.rb scale --help
411
436
  ```
@@ -415,107 +440,218 @@ Usage: fallback_example.rb scale AMOUNT [FACTOR] [--clamp=<CLAMP>] [--notify]
415
440
 
416
441
  Positional arguments:
417
442
  AMOUNT [Integer] required Base amount to process
418
- FACTOR optional (default: 2)
443
+ FACTOR [String] optional (default: 2)
419
444
 
420
445
  Options:
421
446
  --clamp=<CLAMP> [String] optional (default: nil)
422
447
  --notify [Boolean] optional (default: false)
423
448
  ```
424
449
 
425
- 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.
426
-
427
- #### What if the docs mention arguments that do not exist?
450
+ Only `AMOUNT` is documented, yet `factor`, `clamp`, and `notify` are presented
451
+ with inferred defaults and types.
428
452
 
429
- - **Out-of-sync lines fall back to plain text** – Comments that reference non-existent options (for example `--ghost`) or positionals (such as `EXTRA`) are emitted verbatim in the help’s detail section. They do not materialize as real arguments, and strict mode still warns about positional mismatches (`Extra positional argument comments were found: EXTRA`) so you can reconcile the docs.
453
+ Comments never add live parameters by themselves. Lines that reference
454
+ non-existent options (say `--ghost`) or positionals are shown verbatim in the
455
+ help's detail section instead of becoming real arguments, and strict mode warns
456
+ about positional mismatches. For a runnable mismatch demo:
457
+ `rubycli examples/fallback_example_with_extra_docs.rb scale --help`.
430
458
 
431
- > Want to see this behaviour? Try `rubycli examples/fallback_example_with_extra_docs.rb scale --help` for a runnable mismatch demo.
459
+ Run `rubycli --check path/to/script.rb` during development to lint
460
+ documentation drift — including undefined type labels and enum typos, with
461
+ DidYouMean suggestions — and pass `--strict` at runtime when invalid input
462
+ should abort instead of merely warning. `--check` still loads the target file to
463
+ inspect live signatures, so top-level code runs (`examples/hello_app_with_require.rb`
464
+ calls `Rubycli.run` on load, for instance); what it skips is executing the
465
+ selected command.
432
466
 
433
- In short, comments never add live parameters by themselves; they enrich or describe what your method already supports.
467
+ > `--strict` trusts whatever types/choices your comments spell out. Keep
468
+ > `rubycli --check` in CI so documentation typos are caught before production
469
+ > runs that rely on `--strict`.
434
470
 
435
471
  ## Argument parsing modes
436
472
 
437
473
  ### Default literal parsing
438
474
 
439
- Rubycli tries to interpret arguments that look like structured literals (values starting with `{`, `[`, quotes, or YAML front matter) using `Psych.safe_load` before handing them to your code. That means values such as `--names='["Alice","Bob"]'` or `--config='{foo: 1}'` arrive as native arrays / hashes without any extra flags. Plain strings like `1,2,3` stay untouched at this stage (if the documentation declares `String[]` or `TAG...`, a later pass still normalises them into arrays), and unsupported constructs fall back to the original text, so `"2024-01-01"` remains a string and malformed payloads still reach your method instead of killing the run.
475
+ Arguments that look like structured literals (starting with `{`, `[`, quotes,
476
+ or YAML markers) are parsed with `Psych.safe_load`, so
477
+ `--names='["Alice","Bob"]'` or `--config='{foo: 1}'` arrive as native arrays
478
+ and hashes without extra flags. Plain strings like `1,2,3` stay untouched at
479
+ this stage (a later pass normalises them into arrays when the docs declare
480
+ `String[]` or `TAG...`), and unsupported constructs fall back to the original
481
+ text, so `"2024-01-01"` remains a string and malformed payloads still reach
482
+ your method instead of killing the run.
440
483
 
441
- ### JSON mode
484
+ When an argument is documented as a plain `[String]`, the documented conversion
485
+ wins over literal parsing and the raw token is handed over verbatim — quote
486
+ characters included. `--prefix '"quoted"'` therefore arrives as `"quoted"` with
487
+ the quotes, while an undocumented parameter would receive `quoted`.
442
488
 
443
- Supply `--json-args` (or the shorthand `-j`) when invoking the runner and Rubycli will parse subsequent arguments strictly as JSON before passing them to your method:
489
+ ### JSON mode (`--json-args` / `-j`)
444
490
 
445
- ```bash
446
- rubycli -j my_cli.rb MyCLI run '["--config", "{\"foo\":1}"]'
447
- ```
491
+ Parses subsequent arguments strictly as JSON. YAML-only syntax is rejected and
492
+ invalid payloads raise `JSON::ParserError` for callers who want explicit
493
+ failures instead of silent fallbacks. Programmatic equivalent:
494
+ `Rubycli.with_json_mode(true) { ... }`.
448
495
 
449
- This mode rejects YAML-only syntax and raises `JSON::ParserError` when the payload is invalid, which is handy for callers who want explicit failures instead of silent fallbacks. Programmatically you can call `Rubycli.with_json_mode(true) { … }`.
496
+ ### Eval mode (`--eval-args` / `-e`, `--eval-lax` / `-E`)
450
497
 
451
- ## Eval mode
452
-
453
- Use `--eval-args` (or the shorthand `-e`) to evaluate Ruby expressions before they are forwarded to your CLI. This is handy when you want to pass rich objects that are awkward to express as JSON:
498
+ Evaluates each argument as a Ruby expression before it is forwarded, which is
499
+ handy for objects that are awkward as JSON — symbol arrays, ranges, inline
500
+ math:
454
501
 
455
502
  ```bash
456
- rubycli -e scripts/data_cli.rb DataCLI run '(1..10).to_a'
503
+ # symbols and %w literals reach the method as real objects
504
+ rubycli -e --new='%w[x y]' examples/new_mode_runner.rb run --mode ':reverse'
505
+ #=> ["y", "x"]
506
+
507
+ # inline math is evaluated before the documented type conversion runs
508
+ rubycli -e examples/documentation_style_showcase.rb canonical '"Foo"' '2*3'
509
+ #=> {"style": "canonical", "subject": "Foo", "count": 6, ...}
457
510
  ```
458
511
 
459
- 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) { }`.
512
+ Under `--eval-args` **every** argument must be valid Ruby, so a bare word such
513
+ as `--mode summary` aborts; write `':summary'` (or `'"summary"'`) instead, or
514
+ switch to `--eval-lax` below.
460
515
 
461
- Because Ruby evaluation understands symbols, arrays, and hashes, it’s a convenient way to pass literal enum combinations to options that expect arrays:
516
+ Evaluation happens inside an isolated binding
517
+ (`Object.new.instance_eval { binding }`). All eval arguments in one Runner
518
+ execution, including `--new=VALUE` constructor arguments and the selected
519
+ command's arguments, share that binding; it is discarded after the execution.
520
+ Treat this as unsafe input: do not enable it for untrusted callers.
521
+ Programmatic equivalent: `Rubycli.with_eval_mode(true) { ... }`.
522
+
523
+ `--eval-lax` / `-E` behaves like `--eval-args`, but tokens that fail to parse
524
+ as Ruby (for example a bare `https://example.com`) produce a warning and are
525
+ forwarded as the original string — convenient for mixing expressions like
526
+ `60*60*24*14` with plain values:
462
527
 
463
528
  ```bash
464
- rubycli -E scripts/report_runner.rb publish \
465
- --targets '[:marketing, :sales]' \
466
- --channels '[:email, :slack]'
529
+ rubycli -E examples/hello_app.rb greet https://example.com
530
+ #=> [WARN] Failed to evaluate argument as Ruby (...). Passing it through because --eval-lax is enabled.
531
+ #=> Hello, https://example.com!
467
532
  ```
468
533
 
469
- 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.
470
-
471
- `--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.
534
+ `--json-args` cannot be combined with either eval variant; Rubycli raises an
535
+ error if both are present.
472
536
 
473
537
  ## Pre-script bootstrap
474
538
 
475
- Add `--pre-script SRC` (alias: `--init`) when launching the bundled CLI to run arbitrary Ruby code before exposing methods. The code runs inside an isolated binding where the following locals are pre-populated:
539
+ `--pre-script SRC` (alias: `--init`) runs arbitrary Ruby before commands are
540
+ resolved, inside an isolated binding with these locals pre-populated:
476
541
 
477
- - `target` the original class or module (before `--new` instantiation)
478
- - `current` / `instance` the object that would otherwise be exposed (after `--new` if specified)
542
+ - `target` the original class or module (before `--new` instantiation)
543
+ - `current` / `instance` the object that would otherwise be exposed
479
544
 
480
- The last evaluated value becomes the new public target. Returning `nil` keeps the previous object.
545
+ The last evaluated value becomes the new public target (`nil` keeps the
546
+ previous object). `SRC` can be inline Ruby or a file path.
481
547
 
482
- Inline example:
548
+ Example — replace the `--new`-built instance with a hand-built one:
483
549
 
484
550
  ```bash
485
- rubycli --pre-script 'InitArgRunner.new(source: "cli", retries: 2)' \
486
- lib/init_arg_runner.rb summarize --verbose
551
+ rubycli --pre-script 'NewModeRunner.new(%w[a b c], options: {from: :pre})' \
552
+ examples/new_mode_runner.rb run --mode summary
487
553
  ```
488
554
 
489
- File example:
555
+ ## Flags and environment variables
490
556
 
491
- ```bash
492
- # scripts/bootstrap_runner.rb
493
- instance = InitArgRunner.new(source: "preset")
494
- instance.logger = Logger.new($stdout)
495
- instance
496
- ```
557
+ | Flag / Env | Description | Default |
558
+ | ---------- | ----------- | ------- |
559
+ | `--auto-target` / `-a`, `RUBYCLI_AUTO_TARGET=auto` | Auto-select the target constant when the file name does not match | `strict` |
560
+ | `--new[=VALUE]` / `-n[=VALUE]` | Instantiate the class before resolving commands; `VALUE` feeds the constructor's first parameter | off |
561
+ | `--pre-script SRC` / `--init SRC` | Run Ruby code to build/replace the exposed object | off |
562
+ | `--check` / `-c` | Lint documentation/comments without executing commands | off |
563
+ | `--strict` | Enforce documented types/choices; invalid input aborts | off |
564
+ | `--json-args` / `-j` | Parse arguments strictly as JSON | off |
565
+ | `--eval-args` / `-e`, `--eval-lax` / `-E` | Evaluate arguments as Ruby (lax: fall back to the raw string) | off |
566
+ | `--help` / `-h` / `help` | Print the `rubycli` usage message | — |
567
+ | `--print-result`, `RUBYCLI_PRINT_RESULT=true` | Print command return values. The bundled `rubycli` executable already does this, so the flag matters only when you embed `Rubycli.run` yourself | on for `rubycli`, off for `Rubycli.run` |
568
+ | `RUBYCLI_DEBUG=true` | Print debug logs | `false` |
569
+ | `RUBYCLI_ALLOW_PARAM_COMMENT=OFF` | Report YARD `@param` lines as issues during `rubycli --check` (no effect on normal runs) | `ON` |
570
+
571
+ ## Library helpers
572
+
573
+ - `Rubycli.parse_arguments(argv, method)` — parse argv with comment metadata
574
+ - `Rubycli.available_commands(target)` — list CLI-exposable methods
575
+ - `Rubycli.usage_for_method(name, method)` — render usage for a single method
576
+ - `Rubycli.method_description(method)` — fetch structured documentation info
577
+
578
+ ## How it differs from Python Fire
579
+
580
+ - **Comment-aware help** — doc comments enrich the help, but the live method
581
+ signature stays the ultimate authority.
582
+ - **Type-aware parsing** — placeholder syntax and YARD tags coerce arguments to
583
+ booleans, arrays, numerics, and more without additional code.
584
+ - **Two-stage validation** — `--check` lints documentation drift without
585
+ executing commands; `--strict` turns documented types/choices into
586
+ enforceable runtime contracts.
587
+ - **Ruby-centric** — keyword arguments, block documentation (`@yield*` tags),
588
+ and `RUBYCLI_*` environment toggles.
589
+
590
+ | Capability | Python Fire | Rubycli |
591
+ | ---------- | ----------- | ------- |
592
+ | Attribute traversal | Recursively exposes attributes/properties | Exposes public methods on the target; no implicit traversal |
593
+ | Constructor handling | Prompts for `__init__` args automatically | `--new` instantiates; constructor args via `--new=VALUE`, richer wiring via pre-scripts |
594
+ | Interactive shell | Fire-specific REPL when invoked without a command | No interactive shell; strictly command execution |
595
+ | Input discovery | Pure reflection, no doc comments | Doc comments drive option names, placeholders, and validation |
596
+ | Data structures | Dicts/lists become subcommands | Class/module methods only; no automatic dict/list expansion |
597
+
598
+ ## Project philosophy
599
+
600
+ - **Convenience first** — wrap existing Ruby scripts with almost no manual
601
+ plumbing. Fidelity with Python Fire is not a goal; missing Fire features are
602
+ generally by design.
603
+ - **Method definitions first, comments augment** — signatures determine what is
604
+ exposed and what is required; comments refine types, help text, and
605
+ validation.
606
+ - **Maintenance** — much of the implementation was generated with AI
607
+ assistance. The project is no longer maintained; see
608
+ [Project status](#project-status).
609
+
610
+ ## Bundled examples
611
+
612
+ - `examples/hello_app.rb` / `examples/hello_app_with_docs.rb` — minimal
613
+ module-function variants, without and with docs
614
+ - `examples/hello_app_with_require.rb` — embedded `Rubycli.run`
615
+ - `examples/typed_arguments_demo.rb` — stdlib type coercions
616
+ (Date/Time/BigDecimal/Pathname)
617
+ - `examples/strict_choices_demo.rb` — literal enumerations and `--strict`
618
+ - `examples/new_mode_runner.rb` — instance-only class initialized via
619
+ `--new=VALUE`
620
+ - `examples/documentation_style_showcase.rb` — every comment notation in one
621
+ script
622
+ - `examples/fallback_example.rb` / `examples/fallback_example_with_extra_docs.rb`
623
+ — signature fallback and doc-mismatch demos (these two intentionally fail
624
+ `rubycli --check`)
625
+ - `examples/multi_constant_runner.rb` / `examples/mismatched_constant_runner.rb`
626
+ — constant selection: several candidates in one file, and a file whose
627
+ constant does not match its name
628
+
629
+ The commands in this README assume you run them from the repository root. The
630
+ same files ship inside the gem, so after `gem install rubycli` you can locate
631
+ them with `gem contents rubycli`.
632
+
633
+ ## Development
497
634
 
498
635
  ```bash
499
- rubycli --pre-script scripts/bootstrap_runner.rb \
500
- lib/init_arg_runner.rb summarize --verbose
636
+ bundle install # minitest, rake, rubocop — Rubycli itself has no runtime dependencies
637
+ bundle exec rake # test suite + RuboCop
501
638
  ```
502
639
 
503
- This keeps `--new` available for quick zero-argument instantiation while allowing richer bootstrapping when needed.
640
+ Individual tasks:
504
641
 
505
- ## Environment variables & flags
642
+ - `bundle exec rake test` — Minitest suite, including subprocess tests that drive
643
+ the real `exe/rubycli`
644
+ - `bundle exec rake lint` — RuboCop; `.rubocop_todo.yml` tracks the offenses that
645
+ are not fixed yet (mostly method-length metrics in the parsers)
646
+ - `bundle exec rake coverage` — the suite plus the repository coverage thresholds
647
+ (90% overall line coverage, 70% branch coverage, and 90% coverage of executable
648
+ lines changed from `origin/main`)
506
649
 
507
- | Flag / Env | Description | Default |
508
- | ---------- | ----------- | ------- |
509
- | `RUBYCLI_DEBUG=true` | Print debug logs | `false` |
510
- | `--check` | Validate documentation/comments without executing commands | `off` |
511
- | `--strict` | Enforce documented choices/types; invalid input aborts | `off` |
512
- | `RUBYCLI_ALLOW_PARAM_COMMENT=OFF` | Disable legacy `@param` lines (defaults to on today for compatibility) | `ON` |
650
+ The coverage gate is dependency-free, so `ruby -Ilib:test test/coverage_runner.rb`
651
+ also works without Bundler.
513
652
 
514
- ## Library helpers
653
+ ## License
515
654
 
516
- - `Rubycli.parse_arguments(argv, method)` – parse argv with comment metadata
517
- - `Rubycli.available_commands(target)` – list CLI exposable methods
518
- - `Rubycli.usage_for_method(name, method)` – render usage for a single method
519
- - `Rubycli.method_description(method)` – fetch structured documentation info
655
+ MIT. See [LICENSE](LICENSE).
520
656
 
521
- Feedback and issues are welcome while we prepare the public release.
657
+ Feedback and issues are welcome.