clack 0.4.6 → 0.5.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,29 +1,65 @@
1
- # Clack
2
-
3
- **Effortlessly beautiful CLI prompts for Ruby.**
4
-
5
- A faithful Ruby port of [@clack/prompts](https://github.com/bombshell-dev/clack). 16+ prompt types, zero dependencies, Ruby 3.2+. `gem "clack"` and go.
1
+ <p align="center">
2
+ <br>
3
+ <br>
4
+ <code>&nbsp;C&nbsp;L&nbsp;A&nbsp;C&nbsp;K&nbsp;</code>
5
+ <br>
6
+ <br>
7
+ <i>CLI prompts for Ruby. Zero dependencies.</i>
8
+ <br>
9
+ <br>
10
+ <a href="https://rubygems.org/gems/clack"><img src="https://img.shields.io/gem/v/clack?style=flat-square&color=cc342d" alt="Gem Version"></a>
11
+ <a href="https://github.com/swhitt/clackrb/actions"><img src="https://img.shields.io/github/actions/workflow/status/swhitt/clackrb/ci.yml?style=flat-square&label=tests" alt="Tests"></a>
12
+ <a href="https://www.ruby-lang.org"><img src="https://img.shields.io/badge/ruby-3.2%2B-cc342d?style=flat-square" alt="Ruby 3.2+"></a>
13
+ <a href="LICENSE"><img src="https://img.shields.io/badge/license-MIT-blue?style=flat-square" alt="MIT License"></a>
14
+ </p>
6
15
 
7
16
  <p align="center">
8
- <img src="examples/demo.gif?v=2" width="640" alt="Clack demo">
17
+ <img src="examples/demo.gif?v=2" width="640" alt="Clack demo showing beautiful terminal prompts">
9
18
  </p>
10
19
 
20
+ ---
21
+
11
22
  ## Why Clack?
12
23
 
13
- - **Beautiful by default** — Thoughtfully designed prompts that just look right
14
- - **Vim-friendly** — Navigate with `hjkl` or arrow keys
15
- - **Accessible** — Graceful ASCII fallbacks for limited terminals
16
- - **Composable** Group prompts together with `Clack.group`
17
- - **Zero dependencies** — Everything in one gem
24
+ The standard approach:
25
+
26
+ ```ruby
27
+ print "What is your project named? "
28
+ name = gets.chomp
29
+ print "Pick a framework (1=Rails, 2=Sinatra, 3=Roda): "
30
+ framework = gets.chomp.to_i
31
+ ```
32
+
33
+ No navigation, no validation, no visual feedback. With Clack:
34
+
35
+ ```ruby
36
+ require "clack"
37
+
38
+ Clack.intro "create-app"
39
+
40
+ name = Clack.text(message: "What is your project named?", placeholder: "my-app")
41
+ # => Renders a gorgeous, navigable text input with placeholder text
42
+
43
+ framework = Clack.select(
44
+ message: "Pick a framework",
45
+ options: [
46
+ { value: "rails", label: "Ruby on Rails", hint: "recommended" },
47
+ { value: "sinatra", label: "Sinatra" },
48
+ { value: "roda", label: "Roda" }
49
+ ]
50
+ )
51
+ # => Arrow-key navigation, vim bindings, instant submit
52
+
53
+ Clack.outro "You're all set!"
54
+ ```
55
+
56
+ ---
18
57
 
19
58
  ## Installation
20
59
 
21
60
  ```ruby
22
61
  # Gemfile
23
62
  gem "clack"
24
-
25
- # Or from GitHub
26
- gem "clack", github: "swhitt/clackrb"
27
63
  ```
28
64
 
29
65
  ```bash
@@ -31,15 +67,18 @@ gem "clack", github: "swhitt/clackrb"
31
67
  gem install clack
32
68
  ```
33
69
 
70
+ ---
71
+
34
72
  ## Quick Start
35
73
 
36
74
  ```ruby
37
75
  require "clack"
38
76
 
39
- Clack.intro "project-setup"
77
+ Clack.intro "create-app"
40
78
 
41
79
  result = Clack.group do |g|
42
80
  g.prompt(:name) { Clack.text(message: "Project name?", placeholder: "my-app") }
81
+
43
82
  g.prompt(:framework) do
44
83
  Clack.select(
45
84
  message: "Pick a framework",
@@ -50,6 +89,7 @@ result = Clack.group do |g|
50
89
  ]
51
90
  )
52
91
  end
92
+
53
93
  g.prompt(:features) do
54
94
  Clack.multiselect(
55
95
  message: "Select features",
@@ -66,126 +106,16 @@ end
66
106
  Clack.outro "You're all set!"
67
107
  ```
68
108
 
69
- ## Demo
70
-
71
- Try it yourself:
72
-
73
- ```bash
74
- ruby examples/full_demo.rb
75
- ```
76
-
77
- <details>
78
- <summary>Recording the demo GIF</summary>
79
-
80
- Requires [asciinema](https://asciinema.org/), [agg](https://github.com/asciinema/agg), and [expect](https://core.tcl-lang.org/expect/index):
81
-
82
- ```bash
83
- # Record the demo (automated via expect script)
84
- asciinema rec examples/demo.cast --command "expect examples/demo.exp" --overwrite -q
85
-
86
- # Split batched frames to show typing (Ruby buffers terminal output)
87
- ruby examples/split_cast.rb
88
-
89
- # Convert to GIF
90
- agg examples/demo.cast examples/demo.gif --font-size 18 --cols 80 --rows 28 --speed 0.6
91
- ```
92
- </details>
109
+ ---
93
110
 
94
111
  ## Prompts
95
112
 
96
- All prompts return the user's input, or `Clack::CANCEL` if they pressed Escape/Ctrl+C.
97
-
98
- ```ruby
99
- # Check for cancellation
100
- result = Clack.text(message: "Name?")
101
- exit 1 if Clack.cancel?(result)
102
-
103
- # Or use handle_cancel for a one-liner that prints "Cancelled" and returns true
104
- result = Clack.text(message: "Name?")
105
- exit 1 if Clack.handle_cancel(result)
106
-
107
- # With a custom message
108
- exit 1 if Clack.handle_cancel(result, "Aborted by user")
109
- ```
110
-
111
- ### Validation & Transforms
112
-
113
- Prompts support `validate:` and `transform:` options.
114
-
115
- ```
116
- User Input → Validation (raw) → Transform (if valid) → Final Value
117
- ```
118
-
119
- Validation returns an error message, a `Clack::Warning`, or `nil` to pass. Transforms normalize the value after validation passes.
120
-
121
- **Validation results:**
122
- - `nil` or `false` - passes validation
123
- - String - shows error (red), user must fix input
124
- - `Clack::Warning.new(message)` - shows warning (yellow), user can confirm with Enter or edit
125
-
126
- ```ruby
127
- # Use symbol shortcuts (preferred)
128
- name = Clack.text(message: "Name?", transform: :strip)
129
- code = Clack.text(message: "Code?", transform: :upcase)
130
-
131
- # Chain multiple transforms
132
- username = Clack.text(
133
- message: "Username?",
134
- transform: Clack::Transformers.chain(:strip, :downcase)
135
- )
136
-
137
- # Combine validation and transform
138
- amount = Clack.text(
139
- message: "Amount?",
140
- validate: ->(v) { "Must be a number" unless v.match?(/\A\d+\z/) },
141
- transform: :to_integer
142
- )
143
-
144
- # Warning validation (soft failure, user can confirm or edit)
145
- file = Clack.text(
146
- message: "Output file?",
147
- validate: ->(v) { Clack::Warning.new("File exists. Overwrite?") if File.exist?(v) }
148
- )
149
- ```
150
-
151
- Built-in validators and transformers:
152
-
153
- ```ruby
154
- # Validators - return error message, Warning, or nil
155
- Clack::Validators.required # Non-empty input
156
- Clack::Validators.min_length(3) # Minimum character count
157
- Clack::Validators.max_length(100) # Maximum character count
158
- Clack::Validators.format(/\A[a-z]+\z/, "Only lowercase")
159
- Clack::Validators.email # Email format (user@host.tld)
160
- Clack::Validators.url # URL format (http/https)
161
- Clack::Validators.integer # Integer string ("-5", "42")
162
- Clack::Validators.in_range(1..100) # Numeric range (parses as int)
163
- Clack::Validators.one_of(%w[a b c]) # Allowlist check
164
- Clack::Validators.path_exists # File/dir exists on disk
165
- Clack::Validators.directory_exists # Directory exists on disk
166
- Clack::Validators.future_date # Date strictly after today
167
- Clack::Validators.past_date # Date strictly before today
168
- Clack::Validators.date_range(min: d1, max: d2) # Date within range
169
- Clack::Validators.combine(v1, v2) # First error/warning wins
170
-
171
- # Warning validators - allow user to confirm or edit
172
- Clack::Validators.file_exists_warning # For file overwrite confirmations
173
- Clack::Validators.as_warning(validator) # Convert any validator to warning
174
-
175
- # Transformers - normalize the value (use :symbol or Clack::Transformers.name)
176
- :strip / :trim # Remove leading/trailing whitespace
177
- :downcase / :upcase # Change case
178
- :capitalize # "hello world" -> "Hello world"
179
- :titlecase # "hello world" -> "Hello World"
180
- :squish # Collapse whitespace to single spaces
181
- :compact # Remove all whitespace
182
- :to_integer # Parse as integer
183
- :to_float # Parse as float
184
- :digits_only # Extract only digits
185
- ```
113
+ All prompts return the user's input, or `Clack::CANCEL` if the user pressed Escape/Ctrl+C.
186
114
 
187
115
  ### Text
188
116
 
117
+ Single-line text input with placeholders, defaults, validation, and tab completion.
118
+
189
119
  <img src="examples/images/text.svg" alt="Text prompt">
190
120
 
191
121
  ```ruby
@@ -199,16 +129,16 @@ name = Clack.text(
199
129
  )
200
130
  ```
201
131
 
202
- **Tab completion** - press `Tab` to fill the longest common prefix of matching candidates:
132
+ **Tab completion** -- press `Tab` to fill the longest common prefix of matching candidates:
203
133
 
204
134
  ```ruby
205
- # Tab completion from a static list
135
+ # Static list
206
136
  cmd = Clack.text(
207
137
  message: "Command?",
208
138
  completions: %w[build test deploy lint format]
209
139
  )
210
140
 
211
- # Dynamic tab completion
141
+ # Dynamic completions
212
142
  file = Clack.text(
213
143
  message: "File?",
214
144
  completions: ->(input) { Dir.glob("#{input}*") }
@@ -217,6 +147,8 @@ file = Clack.text(
217
147
 
218
148
  ### Password
219
149
 
150
+ Masked text input for secrets and API keys.
151
+
220
152
  <img src="examples/images/password.svg" alt="Password prompt">
221
153
 
222
154
  ```ruby
@@ -228,7 +160,7 @@ secret = Clack.password(
228
160
 
229
161
  ### Multiline Text
230
162
 
231
- Multi-line text input. Enter inserts a newline, Ctrl+D submits.
163
+ For when one line isn't enough. Enter inserts a newline, Ctrl+D submits.
232
164
 
233
165
  ```ruby
234
166
  bio = Clack.multiline_text(
@@ -240,6 +172,8 @@ bio = Clack.multiline_text(
240
172
 
241
173
  ### Confirm
242
174
 
175
+ Yes/no toggle with customizable labels.
176
+
243
177
  <img src="examples/images/confirm.svg" alt="Confirm prompt">
244
178
 
245
179
  ```ruby
@@ -253,7 +187,7 @@ proceed = Clack.confirm(
253
187
 
254
188
  ### Select
255
189
 
256
- Single selection with keyboard navigation.
190
+ Pick one from a list. Navigate with arrow keys or `hjkl`.
257
191
 
258
192
  <img src="examples/images/select.svg" alt="Select prompt">
259
193
 
@@ -272,7 +206,7 @@ db = Clack.select(
272
206
 
273
207
  ### Multiselect
274
208
 
275
- Multiple selections with toggle controls.
209
+ Pick many. Toggle with Space. Select all with `a`. Invert with `i`.
276
210
 
277
211
  <img src="examples/images/multiselect.svg" alt="Multiselect prompt">
278
212
 
@@ -294,14 +228,14 @@ features = Clack.multiselect(
294
228
 
295
229
  ### Autocomplete
296
230
 
297
- Type to filter from a list of options. Filtering uses **fuzzy matching** by default -- characters must appear in order but don't need to be consecutive (e.g. "fb" matches "foobar"). Pass `filter:` to override with custom logic.
231
+ Type to filter with fuzzy matching -- "fb" matches "foobar". Pass `filter:` to override with custom logic.
298
232
 
299
233
  ```ruby
300
234
  color = Clack.autocomplete(
301
235
  message: "Pick a color",
302
236
  options: %w[red orange yellow green blue indigo violet],
303
237
  placeholder: "Type to search...",
304
- max_items: 5 # Default; scrollable via ↑↓
238
+ max_items: 5 # Default; scrollable via up/down arrows
305
239
  )
306
240
 
307
241
  # Custom filter logic (receives option hash and query string)
@@ -312,11 +246,11 @@ cmd = Clack.autocomplete(
312
246
  )
313
247
  ```
314
248
 
315
- > Vim-style `j`/`k`/`h`/`l` navigation is not available all keyboard input feeds into the search field. Use `↑↓` arrow keys to navigate results.
249
+ > Vim-style `j`/`k`/`h`/`l` navigation is not available in autocomplete -- all keyboard input feeds into the search field. Use arrow keys to navigate results.
316
250
 
317
251
  ### Autocomplete Multiselect
318
252
 
319
- Type to filter with multi-selection support.
253
+ Type-to-filter with multi-selection support.
320
254
 
321
255
  ```ruby
322
256
  colors = Clack.autocomplete_multiselect(
@@ -325,17 +259,17 @@ colors = Clack.autocomplete_multiselect(
325
259
  placeholder: "Type to filter...",
326
260
  required: true, # At least one selection required
327
261
  initial_values: ["red"], # Pre-selected values
328
- max_items: 5 # Default; scrollable via ↑↓
262
+ max_items: 5 # Default; scrollable via up/down arrows
329
263
  )
330
264
  ```
331
265
 
332
266
  **Shortcuts:** `Space` toggle | `Enter` confirm
333
267
 
334
- > The `a` (select all), `i` (invert), and `j`/`k`/`h`/`l` shortcuts from `Multiselect` are not available here all keyboard input feeds into the search field instead. Use `↑↓` arrow keys to navigate.
268
+ > The `a` (select all), `i` (invert), and `j`/`k`/`h`/`l` shortcuts from Multiselect are not available here -- all keyboard input feeds into the search field instead. Use arrow keys to navigate.
335
269
 
336
270
  ### Path
337
271
 
338
- File/directory path selector with filesystem navigation.
272
+ Filesystem navigation with Tab completion and arrow key selection.
339
273
 
340
274
  ```ruby
341
275
  project_dir = Clack.path(
@@ -345,7 +279,7 @@ project_dir = Clack.path(
345
279
  )
346
280
  ```
347
281
 
348
- **Navigation:** Type to filter | `Tab` to autocomplete | `↑↓` to select
282
+ **Navigation:** Type to filter | `Tab` to autocomplete | up/down arrows to select
349
283
 
350
284
  ### Date
351
285
 
@@ -362,11 +296,11 @@ date = Clack.date(
362
296
  )
363
297
  ```
364
298
 
365
- **Navigation:** `Tab`/`←→` between segments | `↑↓` adjust value | type digits directly
299
+ **Navigation:** `Tab`/left-right arrows between segments | up/down arrows to adjust value | type digits directly
366
300
 
367
301
  ### Range
368
302
 
369
- Numeric selection with a visual slider track. Navigate with `←→` or `↑↓` arrow keys (or `hjkl`).
303
+ Visual slider for numeric selection.
370
304
 
371
305
  ```ruby
372
306
  volume = Clack.range(
@@ -376,11 +310,12 @@ volume = Clack.range(
376
310
  step: 5,
377
311
  initial_value: 50
378
312
  )
313
+ # Navigate with arrow keys or hjkl
379
314
  ```
380
315
 
381
316
  ### Select Key
382
317
 
383
- Quick selection using keyboard shortcuts.
318
+ Instant selection via keyboard shortcuts. No arrow key navigation needed.
384
319
 
385
320
  ```ruby
386
321
  action = Clack.select_key(
@@ -393,6 +328,34 @@ action = Clack.select_key(
393
328
  )
394
329
  ```
395
330
 
331
+ ### Group Multiselect
332
+
333
+ Multiselect with options organized into named categories.
334
+
335
+ ```ruby
336
+ features = Clack.group_multiselect(
337
+ message: "Select features",
338
+ options: [
339
+ {
340
+ label: "Frontend",
341
+ options: [
342
+ { value: "hotwire", label: "Hotwire" },
343
+ { value: "stimulus", label: "Stimulus" }
344
+ ]
345
+ },
346
+ {
347
+ label: "Background",
348
+ options: [
349
+ { value: "sidekiq", label: "Sidekiq" },
350
+ { value: "solid_queue", label: "Solid Queue" }
351
+ ]
352
+ }
353
+ ],
354
+ selectable_groups: true, # Toggle all options in a group at once
355
+ group_spacing: 1 # Blank lines between groups
356
+ )
357
+ ```
358
+
396
359
  ### Spinner
397
360
 
398
361
  Non-blocking animated indicator for async work.
@@ -411,7 +374,7 @@ spinner.stop("Dependencies installed!")
411
374
  # Or: spinner.cancel("Cancelled")
412
375
  ```
413
376
 
414
- **Block form** - wraps a block with automatic success/error handling:
377
+ **Block form** -- wraps a block with automatic success/error handling:
415
378
 
416
379
  ```ruby
417
380
  result = Clack.spin("Installing dependencies...") { system("npm install") }
@@ -430,7 +393,7 @@ end
430
393
 
431
394
  ### Progress
432
395
 
433
- Visual progress bar for measurable operations.
396
+ A visual progress bar for measurable operations.
434
397
 
435
398
  ```ruby
436
399
  progress = Clack.progress(total: 100, message: "Downloading...")
@@ -446,7 +409,7 @@ progress.stop("Download complete!")
446
409
 
447
410
  ### Tasks
448
411
 
449
- Run multiple tasks with status indicators.
412
+ Run multiple tasks sequentially with status indicators.
450
413
 
451
414
  ```ruby
452
415
  results = Clack.tasks(tasks: [
@@ -472,63 +435,133 @@ Clack.tasks(tasks: [
472
435
  ])
473
436
  ```
474
437
 
475
- ### Group Multiselect
476
-
477
- Multiselect with options organized into groups.
478
-
479
- ```ruby
480
- features = Clack.group_multiselect(
481
- message: "Select features",
482
- options: [
483
- {
484
- label: "Frontend",
485
- options: [
486
- { value: "hotwire", label: "Hotwire" },
487
- { value: "stimulus", label: "Stimulus" }
488
- ]
489
- },
490
- {
491
- label: "Background",
492
- options: [
493
- { value: "sidekiq", label: "Sidekiq" },
494
- { value: "solid_queue", label: "Solid Queue" }
495
- ]
496
- }
497
- ],
498
- selectable_groups: true, # Toggle all options in a group at once
499
- group_spacing: 1 # Blank lines between groups
500
- )
501
- ```
502
-
503
438
  <details>
504
- <summary><h3 style="display:inline">Quick Reference</h3></summary>
439
+ <summary><strong>Quick Reference Table</strong> (click to expand)</summary>
505
440
 
506
441
  | Prompt | Method | Key Options | Defaults |
507
442
  |--------|--------|-------------|----------|
508
- | Text | `Clack.text` | `placeholder:`, `default_value:`, `initial_value:`, `completions:` | |
443
+ | Text | `Clack.text` | `placeholder:`, `default_value:`, `initial_value:`, `completions:` | -- |
509
444
  | Password | `Clack.password` | `mask:`, `validate:` | `mask: "▪"` |
510
445
  | Confirm | `Clack.confirm` | `active:`, `inactive:`, `initial_value:` | `active: "Yes"`, `inactive: "No"`, `initial_value: true` |
511
- | Select | `Clack.select` | `options:`, `initial_value:`, `max_items:` | |
446
+ | Select | `Clack.select` | `options:`, `initial_value:`, `max_items:` | -- |
512
447
  | Multiselect | `Clack.multiselect` | `options:`, `initial_values:`, `required:`, `cursor_at:` | `required: true` |
513
448
  | Group Multiselect | `Clack.group_multiselect` | `options:` (nested), `selectable_groups:`, `group_spacing:` | `selectable_groups: false` |
514
449
  | Autocomplete | `Clack.autocomplete` | `options:`, `placeholder:`, `filter:`, `max_items:` | `max_items: 5` |
515
450
  | Autocomplete Multiselect | `Clack.autocomplete_multiselect` | `options:`, `required:`, `initial_values:`, `filter:` | `required: true`, `max_items: 5` |
516
- | Select Key | `Clack.select_key` | `options:` (with `:key`) | |
451
+ | Select Key | `Clack.select_key` | `options:` (with `:key`) | -- |
517
452
  | Path | `Clack.path` | `root:`, `only_directories:` | `root: "."` |
518
453
  | Date | `Clack.date` | `format:`, `initial_value:`, `min:`, `max:` | `format: :iso` |
519
454
  | Range | `Clack.range` | `min:`, `max:`, `step:`, `initial_value:` | `min: 0`, `max: 100`, `step: 1` |
520
455
  | Multiline Text | `Clack.multiline_text` | `initial_value:`, `validate:` | Submit with **Ctrl+D** |
521
- | Spinner | `Clack.spinner` / `Clack.spin` | block form auto-handles success/error | |
456
+ | Spinner | `Clack.spinner` / `Clack.spin` | block form auto-handles success/error | -- |
522
457
  | Tasks | `Clack.tasks` | `tasks:` (`{title:, task:, enabled:}`) | `enabled: true` |
523
- | Progress | `Clack.progress` | `total:`, `message:` | |
458
+ | Progress | `Clack.progress` | `total:`, `message:` | -- |
524
459
 
525
460
  All prompts accept `message:`, `validate:`, `help:`, and return `Clack::CANCEL` on Escape/Ctrl+C.
526
461
 
527
462
  </details>
528
463
 
464
+ ---
465
+
466
+ ## Cancellation
467
+
468
+ ```ruby
469
+ result = Clack.text(message: "Name?")
470
+ exit 1 if Clack.cancel?(result)
471
+
472
+ # Or the one-liner version (prints "Cancelled" and returns true)
473
+ result = Clack.text(message: "Name?")
474
+ exit 1 if Clack.handle_cancel(result)
475
+
476
+ # With a custom message
477
+ exit 1 if Clack.handle_cancel(result, "Aborted by user")
478
+ ```
479
+
480
+ ---
481
+
482
+ ## Validation and Transforms
483
+
484
+ Every prompt supports `validate:` and `transform:`. The pipeline looks like this:
485
+
486
+ ```
487
+ User Input --> Validation (raw) --> Transform (if valid) --> Final Value
488
+ ```
489
+
490
+ Validation returns an error message, a `Clack::Warning`, or `nil` to pass.
491
+
492
+ **Validation results:**
493
+ - `nil` or `false` -- passes validation
494
+ - String -- shows error (red), user must fix input
495
+ - `Clack::Warning.new(message)` -- shows warning (yellow), user can confirm with Enter or edit
496
+
497
+ ```ruby
498
+ # Symbol shortcuts (clean and idiomatic)
499
+ name = Clack.text(message: "Name?", transform: :strip)
500
+ code = Clack.text(message: "Code?", transform: :upcase)
501
+
502
+ # Chain multiple transforms
503
+ username = Clack.text(
504
+ message: "Username?",
505
+ transform: Clack::Transformers.chain(:strip, :downcase)
506
+ )
507
+
508
+ # Combine validation and transform
509
+ amount = Clack.text(
510
+ message: "Amount?",
511
+ validate: ->(v) { "Must be a number" unless v.match?(/\A\d+\z/) },
512
+ transform: :to_integer
513
+ )
514
+
515
+ # Warning validation (soft failure -- user can confirm or edit)
516
+ file = Clack.text(
517
+ message: "Output file?",
518
+ validate: ->(v) { Clack::Warning.new("File exists. Overwrite?") if File.exist?(v) }
519
+ )
520
+ ```
521
+
522
+ ### Built-in Validators
523
+
524
+ ```ruby
525
+ Clack::Validators.required # Non-empty input
526
+ Clack::Validators.min_length(3) # Minimum character count
527
+ Clack::Validators.max_length(100) # Maximum character count
528
+ Clack::Validators.format(/\A[a-z]+\z/, "Only lowercase")
529
+ Clack::Validators.email # Email format (user@host.tld)
530
+ Clack::Validators.url # URL format (http/https)
531
+ Clack::Validators.integer # Integer string ("-5", "42")
532
+ Clack::Validators.in_range(1..100) # Numeric range (parses as int)
533
+ Clack::Validators.one_of(%w[a b c]) # Allowlist check
534
+ Clack::Validators.path_exists # File/dir exists on disk
535
+ Clack::Validators.directory_exists # Directory exists on disk
536
+ Clack::Validators.future_date # Date strictly after today
537
+ Clack::Validators.past_date # Date strictly before today
538
+ Clack::Validators.date_range(min: d1, max: d2) # Date within range
539
+ Clack::Validators.combine(v1, v2) # First error/warning wins
540
+
541
+ # Warning validators -- allow user to confirm or edit
542
+ Clack::Validators.file_exists_warning # For file overwrite confirmations
543
+ Clack::Validators.as_warning(validator) # Convert any validator to warning
544
+ ```
545
+
546
+ ### Built-in Transformers
547
+
548
+ ```ruby
549
+ :strip / :trim # Remove leading/trailing whitespace
550
+ :downcase / :upcase # Change case
551
+ :capitalize # "hello world" -> "Hello world"
552
+ :titlecase # "hello world" -> "Hello World"
553
+ :squish # Collapse whitespace to single spaces
554
+ :compact # Remove all whitespace
555
+ :to_integer # Parse as integer
556
+ :to_float # Parse as float
557
+ :digits_only # Extract only digits
558
+ ```
559
+
560
+ ---
561
+
529
562
  ## Prompt Groups
530
563
 
531
- Chain multiple prompts and collect results in a hash. Cancellation is handled automatically.
564
+ Chain multiple prompts and collect results in a hash. If the user cancels any prompt, the whole group returns `Clack::CANCEL`.
532
565
 
533
566
  ```ruby
534
567
  result = Clack.group do |g|
@@ -550,9 +583,11 @@ Clack.group(on_cancel: ->(r) { cleanup(r) }) do |g|
550
583
  end
551
584
  ```
552
585
 
553
- ## Logging
586
+ ---
587
+
588
+ ## Pretty Printing
554
589
 
555
- Beautiful, consistent log messages.
590
+ ### Logging
556
591
 
557
592
  ```ruby
558
593
  Clack.log.info("Starting build...")
@@ -579,9 +614,9 @@ success = Clack.stream.command("npm install", type: :info)
579
614
  Clack.stream.success(io_stream)
580
615
  ```
581
616
 
582
- ## Note
617
+ ### Note
583
618
 
584
- Display important information in a box.
619
+ Display important information in a box:
585
620
 
586
621
  ```ruby
587
622
  Clack.note(<<~MSG, title: "Next Steps")
@@ -593,7 +628,7 @@ MSG
593
628
 
594
629
  ### Box
595
630
 
596
- Render a customizable bordered box.
631
+ Render a customizable bordered box:
597
632
 
598
633
  ```ruby
599
634
  Clack.box("Hello, World!", title: "Greeting")
@@ -611,7 +646,7 @@ Clack.box(
611
646
 
612
647
  ### Task Log
613
648
 
614
- Streaming log that clears on success and shows full output on failure. Useful for build output.
649
+ Streaming log that clears on success and shows full output on failure. Great for build output:
615
650
 
616
651
  ```ruby
617
652
  tl = Clack.task_log(title: "Building...", limit: 10)
@@ -626,6 +661,8 @@ tl.success("Build complete!")
626
661
  # tl.error("Build failed!")
627
662
  ```
628
663
 
664
+ ---
665
+
629
666
  ## Session Markers
630
667
 
631
668
  ```ruby
@@ -637,9 +674,9 @@ Clack.outro("Done!") # └ Done!
637
674
  Clack.cancel("Aborted") # └ Aborted (red)
638
675
  ```
639
676
 
640
- ## Configuration
677
+ ---
641
678
 
642
- Customize key bindings and display options globally:
679
+ ## Configuration
643
680
 
644
681
  ```ruby
645
682
  # Add custom key bindings (merged with defaults)
@@ -657,6 +694,8 @@ When CI mode is active, prompts immediately submit with their default values ins
657
694
 
658
695
  Clack also warns when terminal width is below 40 columns, since prompts may not render cleanly in very narrow terminals.
659
696
 
697
+ ---
698
+
660
699
  ## Testing
661
700
 
662
701
  Clack ships with first-class test helpers. Require `clack/testing` explicitly (it is not auto-loaded):
@@ -692,12 +731,40 @@ The `PromptDriver` yielded to the block provides these methods:
692
731
  | `ctrl_d` | Press Ctrl+D (submit multiline text) |
693
732
  | `key(sym_or_char)` | Press an arbitrary key by symbol (e.g. `:escape`) or raw character |
694
733
 
734
+ ---
735
+
736
+ ## Try It
737
+
738
+ ```bash
739
+ ruby examples/full_demo.rb
740
+ ```
741
+
742
+ <details>
743
+ <summary>Recording the demo GIF</summary>
744
+
745
+ Requires [asciinema](https://asciinema.org/), [agg](https://github.com/asciinema/agg), and [expect](https://core.tcl-lang.org/expect/index):
746
+
747
+ ```bash
748
+ # Record the demo (automated via expect script)
749
+ asciinema rec examples/demo.cast --command "expect examples/demo.exp" --overwrite -q
750
+
751
+ # Split batched frames to show typing (Ruby buffers terminal output)
752
+ ruby examples/split_cast.rb
753
+
754
+ # Convert to GIF
755
+ agg examples/demo.cast examples/demo.gif --font-size 18 --cols 80 --rows 28 --speed 0.6
756
+ ```
757
+ </details>
758
+
759
+ ---
760
+
695
761
  ## Requirements
696
762
 
697
763
  - Ruby 3.2+
698
764
  - No runtime dependencies
765
+ - Unicode terminal recommended (ASCII fallbacks included)
699
766
 
700
- ## Development
767
+ ### Development
701
768
 
702
769
  ```bash
703
770
  bundle install
@@ -706,9 +773,11 @@ bundle exec rake spec # Tests only
706
773
  COVERAGE=true bundle exec rake spec # With coverage
707
774
  ```
708
775
 
709
- ## Roadmap
776
+ ### Roadmap
777
+
778
+ - **Wizard mode** -- Multi-step flows with back navigation (`Clack.wizard`). The current `Clack.group` runs prompts as sequential Ruby code, so there's no way to "go back" and re-answer a previous question. A declarative wizard API would define steps as a graph with branching and let the engine handle forward/back navigation.
710
779
 
711
- - **Wizard mode** - Multi-step flows with back navigation (`Clack.wizard`). The current `Clack.group` runs prompts as sequential Ruby code, so there's no way to "go back" and re-answer a previous question. A declarative wizard API would define steps as a graph with branching and let the engine handle forward/back navigation.
780
+ ---
712
781
 
713
782
  ## Credits
714
783
 
@@ -716,4 +785,4 @@ This is a Ruby port of [Clack](https://github.com/bombshell-dev/clack), created
716
785
 
717
786
  ## License
718
787
 
719
- MIT - See [LICENSE](LICENSE)
788
+ MIT -- See [LICENSE](LICENSE)