clack 0.6.2 → 0.7.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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +55 -0
- data/README.md +281 -14
- data/examples/migration_from_tty_prompt.rb +13 -4
- data/lib/clack/box.rb +11 -5
- data/lib/clack/colors.rb +9 -4
- data/lib/clack/core/chrome.rb +139 -0
- data/lib/clack/core/ci_mode.rb +6 -3
- data/lib/clack/core/key_reader.rb +78 -18
- data/lib/clack/core/options_helper.rb +43 -6
- data/lib/clack/core/prompt.rb +47 -72
- data/lib/clack/core/settings.rb +87 -6
- data/lib/clack/environment.rb +28 -4
- data/lib/clack/errors.rb +19 -0
- data/lib/clack/log.rb +31 -17
- data/lib/clack/note.rb +12 -5
- data/lib/clack/prompts/autocomplete.rb +13 -12
- data/lib/clack/prompts/autocomplete_multiselect.rb +12 -21
- data/lib/clack/prompts/confirm.rb +30 -12
- data/lib/clack/prompts/date.rb +1 -1
- data/lib/clack/prompts/group_multiselect.rb +48 -32
- data/lib/clack/prompts/multiline_text.rb +6 -6
- data/lib/clack/prompts/multiselect.rb +12 -19
- data/lib/clack/prompts/password.rb +2 -2
- data/lib/clack/prompts/path.rb +14 -3
- data/lib/clack/prompts/range.rb +37 -4
- data/lib/clack/prompts/select.rb +8 -1
- data/lib/clack/prompts/select_key.rb +53 -30
- data/lib/clack/prompts/spinner.rb +198 -17
- data/lib/clack/prompts/tasks.rb +34 -11
- data/lib/clack/prompts/text.rb +2 -2
- data/lib/clack/stream.rb +38 -18
- data/lib/clack/symbols.rb +6 -1
- data/lib/clack/task_log.rb +24 -8
- data/lib/clack/utils.rb +18 -6
- data/lib/clack/validators.rb +136 -7
- data/lib/clack/version.rb +1 -1
- data/lib/clack.rb +197 -31
- metadata +3 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: fa10e11143746ae1c817d66c8a10b8c0a3a261d8379bd29e71c1134b27560bbc
|
|
4
|
+
data.tar.gz: 925aad6ecb7cf502f56ab9584de97c7b23462121881232b76b09214ecabf7fed
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: ef8452b1824f9df76ffbd3b5d4f8ef35d9be69e83265369171caa5b60c95938f3b0f09b40bdd687d0bf07e555a52abac17a4ef0f1674271f2b9ed27297fb9bca
|
|
7
|
+
data.tar.gz: f5818dfc7b664ef7d25852671c8c3ec4778fca5131f9731fe6bb9be67daa3788d4572c1ab264d9401c1fc60537ebc21c6a4f46a5f0a338e99de73a5eff0cb7ef
|
data/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,60 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [0.7.0] - 2026-08-29
|
|
4
|
+
|
|
5
|
+
### Added
|
|
6
|
+
- `with_guide: false` is now honored everywhere (it was a documented no-op): all prompts, `intro`/`outro`/`cancel`/`handle_cancel`, `log`, `stream`, `note`, `box`, `spinner`/`spin`, `tasks`, and `task_log`. Every prompt and message helper (except `progress`, which never drew a rail) also accepts a per-call `with_guide:` override, matching upstream `withGuide`
|
|
7
|
+
- Keyboard hint footer for `select` (`↑/↓ to navigate • Enter: confirm`), `autocomplete`, and `path`, using upstream wording; `show_instructions: false` per prompt and `Clack.update_settings(show_instructions: false)` hide it; `instructions:` (a String or an Array of Strings) replaces the text on any prompt, e.g. for localized CLIs
|
|
8
|
+
- `Clack.settings` gains `:show_instructions` (default `true`); `Clack::Core::Settings.with_guide?` and the new `show_instructions?` take an optional per-call override
|
|
9
|
+
- Option-based prompts (`select`, `multiselect`, `autocomplete`, `autocomplete_multiselect`, `select_key`) accept a Hash for `options:`: keys are the values, Hash values are labels or `{label:, hint:, disabled:}` specs (`{pg: "PostgreSQL"}` returns `:pg`)
|
|
10
|
+
- `group_multiselect` accepts a Hash of `group label => options`, matching upstream's shape; each group's options accept the same `value => label` Hash
|
|
11
|
+
- `validate:` now accepts a Regexp (`validate: /\A[a-z]+\z/`, fails with "Invalid format"), a built-in validator Symbol (`validate: :email`), an Array of validators (combined, first failure wins), a Hash of `Regexp`/`Symbol => message` (a `Clack::Warning` message makes the entry a soft check), or any object responding to `#call`. Resolved by the new `Clack::Validators.resolve`, mirroring `Transformers.resolve`
|
|
12
|
+
- `Clack::Validators.combine` and `Clack::Validators.as_warning` accept the same shapes (`combine(:required, /\A\d+\z/)`, `as_warning(:email)`)
|
|
13
|
+
- `Clack::Validators::SHORTCUTS` lists the built-ins addressable by Symbol
|
|
14
|
+
- `confirm` accepts `vertical: true` to stack the Yes/No options on separate lines, matching upstream `@clack/prompts` 1.0.1
|
|
15
|
+
- `select_key` accepts `case_sensitive: true` to match option keys exactly, matching upstream 1.0.1; by default `Y` and `y` are still interchangeable
|
|
16
|
+
- `select_key` accepts `initial_value:` to highlight a default option; Enter submits it and CI mode returns it instead of `nil` (an unknown value warns on stderr and is ignored, like `initial_values` elsewhere)
|
|
17
|
+
- Spinner `cancel_message:`, `error_message:`, and `on_cancel:` options, matching upstream `cancelMessage`/`errorMessage`/`onCancel`
|
|
18
|
+
- `Clack.update_settings(messages: {cancel:, error:})` to localize the cancel/error text used by spinners and `Clack.handle_cancel`
|
|
19
|
+
- `Spinner#running?`
|
|
20
|
+
- `Clack::NotATerminalError` (an `IOError`) with an actionable message is raised when a prompt reads from a piped or redirected stdin with CI mode off, replacing a bare `Errno::ENOTTY` from deep inside the key reader
|
|
21
|
+
- `Clack::Core::Settings::KEY_HOME` / `KEY_END` canonical key codes and `Settings.normalize_key`
|
|
22
|
+
|
|
23
|
+
### Changed
|
|
24
|
+
- The guide rail next to the active step is now cyan (the upstream look); the connector above it stays gray and error/warning rails stay yellow. The `help:` line follows the same rule (it was gray in every state), and option rows in `select`, `select_key`, `autocomplete`, and `path` now turn yellow on a validation error too (they stayed gray before). Color only: nothing changes when colors are disabled
|
|
25
|
+
- `select`, `autocomplete`, and `path` frames gain one footer line of keyboard hints by default; snapshot tests of their output need updating (`show_instructions: false` restores the old line count)
|
|
26
|
+
- `multiselect`, `group_multiselect`, and `autocomplete_multiselect` footers use the upstream wording and separator (`↑/↓ to navigate • Space: select • Enter: confirm`, ASCII `up/down` and ` | `) instead of three different dialects; `autocomplete_multiselect` no longer shows its hint line while an error or warning is displayed (the other two already hid it). `multiselect`'s `a`/`i` shortcuts still work but are documented in the README instead of the footer
|
|
27
|
+
- `box` renders inside the guide gutter (a gray `│ ` on each line) when guides are on, matching upstream
|
|
28
|
+
- `Spinner#cancel` and `Spinner#error` called without a message now show the configured cancel/error message ("Cancelled" / "Something went wrong") instead of echoing the in-progress text
|
|
29
|
+
- A spinner still running when the process exits (`exit`, Ctrl+C, an uncaught exception) now prints its cancel line (or error line) before the `at_exit` blocks your program registered earlier, instead of leaving a half-drawn frame
|
|
30
|
+
- `ci_mode: :auto` checks the prompt's own `input:` stream instead of `$stdin`, so a `StringIO` or other non-TTY input auto-submits defaults
|
|
31
|
+
- Escape sequences are parsed up to their CSI final byte, so a key typed right after an arrow key is no longer swallowed into the sequence
|
|
32
|
+
- Key codes are normalized before alias lookup, so custom `aliases:` must be keyed on the canonical codes (`"\e[A"`, `Settings::KEY_HOME`); the SS3 and tilde variants (`\eOA`, `\e[1~`) are folded before the lookup and cannot be aliased directly
|
|
33
|
+
|
|
34
|
+
### Fixed
|
|
35
|
+
- Passing a Hash as `options:` no longer returns `[key, label]` pairs as values (`select`, `multiselect`, `autocomplete`) or raises `TypeError` (`select_key`, `group_multiselect`)
|
|
36
|
+
- `group_multiselect` raises `ArgumentError` naming the group when a group's `options:` is not an option list (`nil`, a String, an Integer), instead of `NoMethodError`
|
|
37
|
+
- `group_multiselect` now renders `Clack.warning` validation results; previously the first Enter looked dead and the second submitted without ever showing the message
|
|
38
|
+
- `validate:` given an unsupported value (an Integer, an unknown Symbol) now raises `ArgumentError` when the prompt is constructed, instead of `NoMethodError` at submit time after the user has typed
|
|
39
|
+
- `Validators.combine` no longer crashes at call time on `nil` entries (they are skipped)
|
|
40
|
+
- `confirm` coerces `initial_value:` to a Boolean, so `initial_value: nil` returns `false` on Enter instead of `nil` (interactive and CI mode), and truthy non-Boolean values return `true`
|
|
41
|
+
- `select_key` now honors `validate:` and `transform:` (they were accepted and silently ignored) and renders validation errors and warnings in its footer like every other prompt
|
|
42
|
+
- `select_key` no longer crashes on the first keypress when an option's `key:` is not a String
|
|
43
|
+
- CI mode confirms a `Clack.warning` validation result the way a second Enter does, so `transform:` is applied; previously the untransformed value was returned with the prompt left in `:warning` state
|
|
44
|
+
- `range` with a fractional `step` (0.1, 0.05, ...) now snaps exactly instead of accumulating Float error, so three 0.1 steps return and display `0.3` rather than `0.30000000000000004`; an `initial_value` exactly halfway between two steps now rounds up to the next step (`0.15` on a `0.1` grid is `0.2`)
|
|
45
|
+
- `Clack.spin` no longer leaves the animation thread running (repainting over later output) when the block calls `exit`, raises `Interrupt` or a non-`StandardError`, or exits via `break`/`throw`; it prints the final line and re-raises
|
|
46
|
+
- `Clack.tasks` finishes its spinner when a task calls `exit`, raises `Interrupt`, or throws
|
|
47
|
+
- Arrow keys in application cursor mode (tmux, after vim/less) no longer type a literal `A`/`B`/`C`/`D`: SS3 sequences (`\eOA`) are assembled and normalized to `\e[A`
|
|
48
|
+
- Home/End variants (`\e[H`, `\eOH`, `\e[1~`, `\e[7~` and the End equivalents) are normalized to one code per key
|
|
49
|
+
- End of input on a piped or `StringIO` input now cancels the prompt instead of spinning the run loop at 100% CPU
|
|
50
|
+
- `FORCE_COLOR=0` (and `FORCE_COLOR=false`) now disables colors, cursor sequences and Unicode symbols instead of forcing them on
|
|
51
|
+
- An empty `NO_COLOR=` is treated as unset, per no-color.org; an empty `FORCE_COLOR=` is likewise ignored
|
|
52
|
+
- Redrawing a frame whose lines soft-wrap in a narrow terminal (for example the keyboard hint footer below about 51 columns) no longer leaves orphaned rail lines above the prompt
|
|
53
|
+
- A child created with a block-less `fork` inside `Clack.spin` no longer prints the parent's cancel line or runs `on_cancel` when it exits
|
|
54
|
+
|
|
55
|
+
### Docs
|
|
56
|
+
- README gains Troubleshooting, Validation Shorthands, Environment variables (`NO_COLOR`, `FORCE_COLOR`, `CLACK_UNICODE`, `CLACK_ESCAPE_TIMEOUT`), Guide rail, and Keyboard hints sections; ARCHITECTURE.md input, color, symbol, spinner, and chrome descriptions now match the code. The README screenshots predate the cyan rail and the `select` footer
|
|
57
|
+
|
|
3
58
|
## [0.6.2] - 2026-08-13
|
|
4
59
|
|
|
5
60
|
### Added
|
data/README.md
CHANGED
|
@@ -115,6 +115,8 @@ Clack.outro "You're all set!"
|
|
|
115
115
|
|
|
116
116
|
All prompts return the user's input, or `Clack::CANCEL` if the user pressed Escape/Ctrl+C.
|
|
117
117
|
|
|
118
|
+
> The demo GIF and the screenshots below were recorded before 0.7.0, so they still show a gray rail next to the active step and no keyboard hint footer under `select`. See [Guide rail](#guide-rail) and [Keyboard hints](#keyboard-hints) for the current look.
|
|
119
|
+
|
|
118
120
|
### Text
|
|
119
121
|
|
|
120
122
|
Single-line text input with placeholders, defaults, validation, and tab completion.
|
|
@@ -188,9 +190,27 @@ proceed = Clack.confirm(
|
|
|
188
190
|
)
|
|
189
191
|
```
|
|
190
192
|
|
|
193
|
+
Long labels read better stacked. Pass `vertical: true` to put each option on its own line (up/down, left/right, and `y`/`n` all still work):
|
|
194
|
+
|
|
195
|
+
```ruby
|
|
196
|
+
Clack.confirm(
|
|
197
|
+
message: "Overwrite ~/.zshrc?",
|
|
198
|
+
active: "Yes, back it up and replace it",
|
|
199
|
+
inactive: "No, keep my existing file",
|
|
200
|
+
initial_value: false,
|
|
201
|
+
vertical: true
|
|
202
|
+
)
|
|
203
|
+
# ◆ Overwrite ~/.zshrc?
|
|
204
|
+
# │ ○ Yes, back it up and replace it
|
|
205
|
+
# │ ● No, keep my existing file
|
|
206
|
+
# └
|
|
207
|
+
```
|
|
208
|
+
|
|
209
|
+
`initial_value:` is coerced to a Boolean, so `nil` (for example an unset CLI flag) starts on "No" and the prompt returns `true`, `false`, or `Clack::CANCEL` (unless you pass a `transform:`).
|
|
210
|
+
|
|
191
211
|
### Select
|
|
192
212
|
|
|
193
|
-
Pick one from a list. Navigate with arrow keys or `hjkl`.
|
|
213
|
+
Pick one from a list. Navigate with arrow keys or `hjkl`. A footer of keyboard hints (key names dimmed) lists the keys (`↑/↓ to navigate • Enter: confirm`); pass `show_instructions: false` to hide it.
|
|
194
214
|
|
|
195
215
|
<img src="examples/images/select.svg" alt="Select prompt">
|
|
196
216
|
|
|
@@ -207,6 +227,31 @@ db = Clack.select(
|
|
|
207
227
|
)
|
|
208
228
|
```
|
|
209
229
|
|
|
230
|
+
#### Option shorthands
|
|
231
|
+
|
|
232
|
+
Every option-based prompt also accepts a Hash instead of an Array. For `select`, `multiselect`, `autocomplete`, `autocomplete_multiselect`, and `select_key`, keys are the values you get back, Hash values are the labels, and insertion order is the display order:
|
|
233
|
+
|
|
234
|
+
```ruby
|
|
235
|
+
db = Clack.select(message: "Choose a database", options: {pg: "PostgreSQL", mysql: "MySQL", sqlite: "SQLite"})
|
|
236
|
+
# => :pg
|
|
237
|
+
|
|
238
|
+
# Use a nested Hash when you need a hint or a disabled entry
|
|
239
|
+
editor = Clack.select(
|
|
240
|
+
message: "Editor?",
|
|
241
|
+
options: {
|
|
242
|
+
nvim: { label: "Neovim", hint: "recommended" },
|
|
243
|
+
vim: "Vim",
|
|
244
|
+
nano: { label: "nano", disabled: true }
|
|
245
|
+
}
|
|
246
|
+
)
|
|
247
|
+
|
|
248
|
+
features = Clack.multiselect(message: "Features", options: {api: "API", auth: "Auth"}, initial_values: [:api])
|
|
249
|
+
```
|
|
250
|
+
|
|
251
|
+
`group_multiselect` takes `group label => options` at the top level; each group's options follow the same `value => label` rule (see [Group Multiselect](#group-multiselect)).
|
|
252
|
+
|
|
253
|
+
The orientation is always `value => label`. A `label => value` Hash (the Rails `options_for_select` shape) is not detected; flip it with `.invert` first. Arrays of `[label, value]` pairs are not detected either, since Array-valued options are legal; use `.to_h` to build a Hash instead. A single `{ value:, label: }` option Hash must still be wrapped in an Array; passed bare, it is read as two `value => label` pairs.
|
|
254
|
+
|
|
210
255
|
### Multiselect
|
|
211
256
|
|
|
212
257
|
Pick many. Toggle with Space. Select all with `a`. Invert with `i`.
|
|
@@ -227,7 +272,7 @@ features = Clack.multiselect(
|
|
|
227
272
|
)
|
|
228
273
|
```
|
|
229
274
|
|
|
230
|
-
**Shortcuts:** `Space`
|
|
275
|
+
**Shortcuts:** `Space` select | `a` all | `i` invert | `Enter` confirm (the footer lists `Space` and `Enter`; `a` and `i` are extras not shown there; `show_instructions: false` hides the footer)
|
|
231
276
|
|
|
232
277
|
### Autocomplete
|
|
233
278
|
|
|
@@ -267,7 +312,7 @@ colors = Clack.autocomplete_multiselect(
|
|
|
267
312
|
)
|
|
268
313
|
```
|
|
269
314
|
|
|
270
|
-
**Shortcuts:** `Space`
|
|
315
|
+
**Shortcuts:** `Space` select | `Enter` confirm | type to search
|
|
271
316
|
|
|
272
317
|
> 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.
|
|
273
318
|
|
|
@@ -283,7 +328,7 @@ project_dir = Clack.path(
|
|
|
283
328
|
)
|
|
284
329
|
```
|
|
285
330
|
|
|
286
|
-
**Navigation:** Type to filter | `Tab` to
|
|
331
|
+
**Navigation:** Type to filter | `Tab` to complete | `↑/↓` to select (all listed in the footer)
|
|
287
332
|
|
|
288
333
|
### Date
|
|
289
334
|
|
|
@@ -315,8 +360,13 @@ volume = Clack.range(
|
|
|
315
360
|
initial_value: 50
|
|
316
361
|
)
|
|
317
362
|
# Navigate with arrow keys or hjkl
|
|
363
|
+
|
|
364
|
+
# Fractional steps snap exactly: three steps from 0 is 0.3, not 0.30000000000000004
|
|
365
|
+
opacity = Clack.range(message: "Opacity", min: 0, max: 1, step: 0.1)
|
|
318
366
|
```
|
|
319
367
|
|
|
368
|
+
Returns an `Integer` when `min` and `step` are both integers, otherwise a `Float`.
|
|
369
|
+
|
|
320
370
|
### Select Key
|
|
321
371
|
|
|
322
372
|
Instant selection via keyboard shortcuts. No arrow key navigation needed.
|
|
@@ -332,6 +382,36 @@ action = Clack.select_key(
|
|
|
332
382
|
)
|
|
333
383
|
```
|
|
334
384
|
|
|
385
|
+
Keys match regardless of case by default. Pass `case_sensitive: true` to tell `Y` and `y` apart, and `initial_value:` to highlight a default that Enter accepts (it is also what CI mode returns). The highlight is a color effect, so when colors are off (`NO_COLOR`, piped output) mark the default in that option's `hint:` as well. `validate:` and `transform:` work like everywhere else.
|
|
386
|
+
|
|
387
|
+
```ruby
|
|
388
|
+
answer = Clack.select_key(
|
|
389
|
+
message: "Apply migration?",
|
|
390
|
+
options: [
|
|
391
|
+
{ value: :yes_all, label: "Yes to all", key: "Y" },
|
|
392
|
+
{ value: :yes, label: "Yes", key: "y" },
|
|
393
|
+
{ value: :no, label: "No", key: "n", hint: "default" }
|
|
394
|
+
],
|
|
395
|
+
case_sensitive: true,
|
|
396
|
+
initial_value: :no,
|
|
397
|
+
validate: ->(v) { "Not on Fridays" if v == :yes_all && Date.today.friday? }
|
|
398
|
+
)
|
|
399
|
+
```
|
|
400
|
+
|
|
401
|
+
With the Hash shorthand the key defaults to the first character of the value. Pass a nested Hash to set it explicitly (`initial_value:` and `case_sensitive:` work the same way):
|
|
402
|
+
|
|
403
|
+
```ruby
|
|
404
|
+
action = Clack.select_key(
|
|
405
|
+
message: "What would you like to do?",
|
|
406
|
+
options: {
|
|
407
|
+
create: { label: "Create new project", key: "c" },
|
|
408
|
+
open: "Open existing", # key "o"
|
|
409
|
+
quit: "Quit" # key "q"
|
|
410
|
+
},
|
|
411
|
+
initial_value: :open
|
|
412
|
+
)
|
|
413
|
+
```
|
|
414
|
+
|
|
335
415
|
### Group Multiselect
|
|
336
416
|
|
|
337
417
|
Multiselect with options organized into named categories.
|
|
@@ -360,6 +440,19 @@ features = Clack.group_multiselect(
|
|
|
360
440
|
)
|
|
361
441
|
```
|
|
362
442
|
|
|
443
|
+
Groups can also be a Hash of `group label => options`, and each group's options accept the same `value => label` shorthand (or a plain Array):
|
|
444
|
+
|
|
445
|
+
```ruby
|
|
446
|
+
addons = Clack.group_multiselect(
|
|
447
|
+
message: "Add-ons",
|
|
448
|
+
options: {
|
|
449
|
+
"Observability" => { prom: "Prometheus", dd: "Datadog" },
|
|
450
|
+
"Storage" => { s3: "S3", gcs: "GCS" }
|
|
451
|
+
}
|
|
452
|
+
)
|
|
453
|
+
# => [:prom, :s3]
|
|
454
|
+
```
|
|
455
|
+
|
|
363
456
|
### Spinner
|
|
364
457
|
|
|
365
458
|
Non-blocking animated indicator for async work.
|
|
@@ -395,6 +488,27 @@ Clack.spin("Working...") do |s|
|
|
|
395
488
|
end
|
|
396
489
|
```
|
|
397
490
|
|
|
491
|
+
**Cancel and error messages** -- `cancel`/`error` with no argument, Ctrl+C, and `exit` use these instead of the in-progress text:
|
|
492
|
+
|
|
493
|
+
```ruby
|
|
494
|
+
s = Clack.spinner(
|
|
495
|
+
cancel_message: "Deploy aborted", # default: "Cancelled"
|
|
496
|
+
error_message: "Deploy failed", # default: "Something went wrong"
|
|
497
|
+
on_cancel: -> { release_lock } # runs once after a cancel, no arguments
|
|
498
|
+
)
|
|
499
|
+
s.start("Deploying")
|
|
500
|
+
s.cancel # => "■ Deploy aborted", on_cancel runs
|
|
501
|
+
```
|
|
502
|
+
|
|
503
|
+
Inside `Clack.spin`, an exception raised by the block prints `error:` or the exception message; `error_message:` only applies to a bare `s.error` or an uncaught crash at process exit.
|
|
504
|
+
|
|
505
|
+
**Ctrl+C and `exit` are safe.** A spinner that is still running when the process exits (Ctrl+C, `exit`, an uncaught error) prints its cancel or error line and stops animating before the `at_exit` blocks your program registered earlier, instead of leaving a half-drawn frame. `Clack.spin` does the same when its block exits early (`exit`, `break`, `throw`, `Interrupt`):
|
|
506
|
+
|
|
507
|
+
```ruby
|
|
508
|
+
Clack.spin("Building", cancel_message: "Build cancelled") { exit 2 }
|
|
509
|
+
# ■ Build cancelled (then the process exits with status 2)
|
|
510
|
+
```
|
|
511
|
+
|
|
398
512
|
### Progress
|
|
399
513
|
|
|
400
514
|
A visual progress bar for measurable operations.
|
|
@@ -446,22 +560,22 @@ Clack.tasks(tasks: [
|
|
|
446
560
|
|--------|--------|-------------|----------|
|
|
447
561
|
| Text | `Clack.text` | `placeholder:`, `default_value:`, `initial_value:`, `completions:` | -- |
|
|
448
562
|
| Password | `Clack.password` | `mask:`, `validate:` | `mask: "▪"` |
|
|
449
|
-
| Confirm | `Clack.confirm` | `active:`, `inactive:`, `initial_value:` | `active: "Yes"`, `inactive: "No"`, `initial_value: true` |
|
|
450
|
-
| Select | `Clack.select` | `options
|
|
451
|
-
| Multiselect | `Clack.multiselect` | `options:`, `initial_values:`, `required:`, `cursor_at:` | `required: true` |
|
|
563
|
+
| Confirm | `Clack.confirm` | `active:`, `inactive:`, `initial_value:`, `vertical:` | `active: "Yes"`, `inactive: "No"`, `initial_value: true`, `vertical: false` |
|
|
564
|
+
| Select | `Clack.select` | `options:` (Array or Hash), `initial_value:`, `max_items:`, `show_instructions:` | -- |
|
|
565
|
+
| Multiselect | `Clack.multiselect` | `options:`, `initial_values:`, `required:`, `cursor_at:`, `show_instructions:` | `required: true` |
|
|
452
566
|
| Group Multiselect | `Clack.group_multiselect` | `options:` (nested), `selectable_groups:`, `group_spacing:` | `selectable_groups: false` |
|
|
453
567
|
| Autocomplete | `Clack.autocomplete` | `options:`, `placeholder:`, `filter:`, `max_items:` | `max_items: 5` |
|
|
454
568
|
| Autocomplete Multiselect | `Clack.autocomplete_multiselect` | `options:`, `required:`, `initial_values:`, `filter:` | `required: true`, `max_items: 5` |
|
|
455
|
-
| Select Key | `Clack.select_key` | `options:` (with `:key`) |
|
|
569
|
+
| Select Key | `Clack.select_key` | `options:` (with `:key`), `case_sensitive:`, `initial_value:` | `case_sensitive: false` |
|
|
456
570
|
| Path | `Clack.path` | `root:`, `only_directories:` | `root: "."` |
|
|
457
571
|
| Date | `Clack.date` | `format:`, `initial_value:`, `min:`, `max:` | `format: :iso` |
|
|
458
572
|
| Range | `Clack.range` | `min:`, `max:`, `step:`, `initial_value:` | `min: 0`, `max: 100`, `step: 1` |
|
|
459
573
|
| Multiline Text | `Clack.multiline_text` | `initial_value:`, `validate:` | Submit with **Ctrl+D** |
|
|
460
|
-
| Spinner | `Clack.spinner` / `Clack.spin` |
|
|
574
|
+
| Spinner | `Clack.spinner` / `Clack.spin` | `indicator:`, `cancel_message:`, `error_message:`, `on_cancel:` | `indicator: :dots` |
|
|
461
575
|
| Tasks | `Clack.tasks` | `tasks:` (`{title:, task:, enabled:}`) | `enabled: true` |
|
|
462
576
|
| Progress | `Clack.progress` | `total:`, `message:` | -- |
|
|
463
577
|
|
|
464
|
-
All prompts accept `message:`, `validate
|
|
578
|
+
All prompts accept `message:`, `validate:` (a proc, Regexp, Symbol, Array, or Hash; see [Validation Shorthands](#validation-shorthands)), `help:`, `with_guide:`, `instructions:`, and return `Clack::CANCEL` on Escape/Ctrl+C.
|
|
465
579
|
|
|
466
580
|
</details>
|
|
467
581
|
|
|
@@ -479,6 +593,8 @@ exit 1 if Clack.handle_cancel(result)
|
|
|
479
593
|
|
|
480
594
|
# With a custom message
|
|
481
595
|
exit 1 if Clack.handle_cancel(result, "Aborted by user")
|
|
596
|
+
|
|
597
|
+
# The default "Cancelled" text follows `Clack.update_settings(messages: {cancel: ...})`
|
|
482
598
|
```
|
|
483
599
|
|
|
484
600
|
---
|
|
@@ -523,6 +639,51 @@ file = Clack.text(
|
|
|
523
639
|
)
|
|
524
640
|
```
|
|
525
641
|
|
|
642
|
+
### Validation Shorthands
|
|
643
|
+
|
|
644
|
+
`validate:` takes more than a lambda. Everything below is normalized by `Clack::Validators.resolve` when the prompt is built, so a typo raises `ArgumentError` immediately instead of crashing after the user has typed.
|
|
645
|
+
|
|
646
|
+
```ruby
|
|
647
|
+
# Regexp: fails with "Invalid format" unless the whole value matches
|
|
648
|
+
slug = Clack.text(message: "Slug?", validate: /\A[a-z0-9-]+\z/)
|
|
649
|
+
|
|
650
|
+
# Symbol: a zero-argument built-in validator
|
|
651
|
+
email = Clack.text(message: "Email?", validate: :email)
|
|
652
|
+
|
|
653
|
+
# Hash: pattern (or symbol) => custom message, checked in order
|
|
654
|
+
user = Clack.text(
|
|
655
|
+
message: "Username?",
|
|
656
|
+
validate: {
|
|
657
|
+
required: "Username is required",
|
|
658
|
+
/\A[a-z0-9_]+\z/ => "Lowercase letters, numbers, and underscores only"
|
|
659
|
+
}
|
|
660
|
+
)
|
|
661
|
+
|
|
662
|
+
# Array: combine validators, first failure wins (nil or false entries are skipped)
|
|
663
|
+
handle = Clack.text(
|
|
664
|
+
message: "Handle?",
|
|
665
|
+
validate: [:required, /\A\w+\z/, ->(v) { "Taken" if TAKEN.include?(v) }]
|
|
666
|
+
)
|
|
667
|
+
|
|
668
|
+
# Anything that responds to #call: procs, Method objects, service objects
|
|
669
|
+
port = Clack.text(message: "Port?", validate: method(:check_port))
|
|
670
|
+
```
|
|
671
|
+
|
|
672
|
+
Symbols available as shorthands: `:required`, `:email`, `:url`, `:integer` (any text-style prompt); `:path_exists`, `:directory_exists`, `:file_exists_warning` (a path string, so `text` or `path`); `:future_date`, `:past_date` (a `Date`, so the `date` prompt). They run against the prompt's raw value, so `:future_date` on a text prompt raises at submit. Built-ins that take arguments (`min_length`, `in_range`, `one_of`, ...) are called explicitly and mixed in: `validate: [:required, Clack::Validators.min_length(3)]`.
|
|
673
|
+
|
|
674
|
+
Regexps are checked with `match?` against `value.to_s`, so anchor with `\A` and `\z` (not `^`/`$`) unless you want per-line matching. A Regexp always means "the value must match"; to soften the check, use a `Clack::Warning` as the Hash message: `{/\A[a-z0-9-]+\z/ => Clack.warning("Unusual characters, continue?")}` warns instead of blocking when the value doesn't match. The same works for symbol keys: `{file_exists_warning: Clack.warning("Overwrite?")}`.
|
|
675
|
+
|
|
676
|
+
`Clack::Validators.combine` and `Clack::Validators.as_warning` accept the same shapes: `combine(:required, /\A\d+\z/)`, `as_warning(:email)`.
|
|
677
|
+
|
|
678
|
+
Using a schema library? Wrap it in a lambda that returns the first error message:
|
|
679
|
+
|
|
680
|
+
```ruby
|
|
681
|
+
contract = SignupContract.new # dry-validation
|
|
682
|
+
Clack.text(message: "Email?", validate: ->(v) { contract.call(email: v).errors[:email]&.first })
|
|
683
|
+
```
|
|
684
|
+
|
|
685
|
+
Don't pass the contract itself: its `call` returns a Result object, which is always truthy, so the prompt would treat every submit as an error.
|
|
686
|
+
|
|
526
687
|
### Built-in Validators
|
|
527
688
|
|
|
528
689
|
```ruby
|
|
@@ -545,6 +706,10 @@ Clack::Validators.combine(v1, v2) # First error/warning wins
|
|
|
545
706
|
# Warning validators -- allow user to confirm or edit
|
|
546
707
|
Clack::Validators.file_exists_warning # For file overwrite confirmations
|
|
547
708
|
Clack::Validators.as_warning(validator) # Convert any validator to warning
|
|
709
|
+
|
|
710
|
+
# Zero-argument validators above also work as bare symbols or Hash keys:
|
|
711
|
+
Clack.text(message: "Email?", validate: :email)
|
|
712
|
+
Clack.text(message: "Email?", validate: {email: "That doesn't look right"})
|
|
548
713
|
```
|
|
549
714
|
|
|
550
715
|
### Built-in Transformers
|
|
@@ -602,6 +767,8 @@ Clack.log.step("Running migrations")
|
|
|
602
767
|
Clack.log.message("Custom message")
|
|
603
768
|
```
|
|
604
769
|
|
|
770
|
+
`Clack.log.*` and `Clack.stream.*` accept `with_guide: false` (or follow the global setting). The level symbol stays on the first line; only the continuation rail goes away.
|
|
771
|
+
|
|
605
772
|
### Stream
|
|
606
773
|
|
|
607
774
|
Stream output from iterables, enumerables, or shell commands:
|
|
@@ -648,6 +815,8 @@ Clack.box(
|
|
|
648
815
|
)
|
|
649
816
|
```
|
|
650
817
|
|
|
818
|
+
Boxes sit inside the guide rail (a gray `│ ` on every line) so they line up with the prompts around them; pass `with_guide: false` to render one flush left.
|
|
819
|
+
|
|
651
820
|
### Task Log
|
|
652
821
|
|
|
653
822
|
Streaming log that clears on success and shows full output on failure. Great for build output:
|
|
@@ -676,6 +845,8 @@ Clack.outro("Done!") # └ Done!
|
|
|
676
845
|
|
|
677
846
|
# Or on error:
|
|
678
847
|
Clack.cancel("Aborted") # └ Aborted (red)
|
|
848
|
+
|
|
849
|
+
# with_guide: false drops the ┌ │ └ symbols on all three
|
|
679
850
|
```
|
|
680
851
|
|
|
681
852
|
---
|
|
@@ -686,17 +857,80 @@ Clack.cancel("Aborted") # └ Aborted (red)
|
|
|
686
857
|
# Add custom key bindings (merged with defaults)
|
|
687
858
|
Clack.update_settings(aliases: { "y" => :enter, "n" => :cancel })
|
|
688
859
|
|
|
689
|
-
#
|
|
860
|
+
# Hide the guide rail everywhere (per-call with_guide: overrides this)
|
|
690
861
|
Clack.update_settings(with_guide: false)
|
|
691
862
|
|
|
863
|
+
# Hide the keyboard hint footer under list prompts
|
|
864
|
+
Clack.update_settings(show_instructions: false)
|
|
865
|
+
|
|
866
|
+
# Localize the cancel/error text used by spinners and Clack.handle_cancel
|
|
867
|
+
Clack.update_settings(messages: { cancel: "Abgebrochen", error: "Etwas ging schief" })
|
|
868
|
+
|
|
692
869
|
# CI / non-interactive mode (prompts auto-submit with defaults)
|
|
693
870
|
Clack.update_settings(ci_mode: true) # Always on
|
|
694
|
-
Clack.update_settings(ci_mode: :auto) # Auto-detect (non-TTY or CI env vars)
|
|
871
|
+
Clack.update_settings(ci_mode: :auto) # Auto-detect (piped/non-TTY input or CI env vars)
|
|
695
872
|
```
|
|
696
873
|
|
|
697
|
-
When CI mode is active, prompts immediately submit with their default values instead of waiting for input. Useful for CI pipelines and scripted environments where stdin is not a TTY.
|
|
874
|
+
When CI mode is active, prompts immediately submit with their default values instead of waiting for input. Useful for CI pipelines and scripted environments where stdin is not a TTY. With `:auto`, detection looks at the prompt's own `input:` stream (stdin by default), so a `StringIO` input auto-submits as well.
|
|
698
875
|
|
|
699
|
-
|
|
876
|
+
Without CI mode, prompting on a piped or redirected stdin raises `Clack::NotATerminalError` (see [Troubleshooting](#troubleshooting)).
|
|
877
|
+
|
|
878
|
+
Clack also warns when terminal width is below 40 columns, since prompts may not render cleanly in very narrow terminals. Keyboard hint footers soft-wrap below roughly 51 columns rather than corrupting the frame; pass `show_instructions: false` or a shorter `instructions:` to avoid the wrap.
|
|
879
|
+
|
|
880
|
+
### Environment variables
|
|
881
|
+
|
|
882
|
+
Color, cursor and symbol output are auto-detected from the terminal. Override with the usual variables:
|
|
883
|
+
|
|
884
|
+
| Variable | Effect |
|
|
885
|
+
|---|---|
|
|
886
|
+
| `NO_COLOR=1` | Disable ANSI colors and cursor sequences, use ASCII symbols. Any non-empty value works; an empty value is ignored (per [no-color.org](https://no-color.org)). |
|
|
887
|
+
| `FORCE_COLOR=1` | Enable colors even when output is piped or `TERM=dumb`, e.g. to keep colors in a CI log. `NO_COLOR` still wins. |
|
|
888
|
+
| `FORCE_COLOR=0` | Disable colors everywhere, same as `NO_COLOR=1`. `false` works too. |
|
|
889
|
+
| `CLACK_UNICODE=1` / `0` | Force Unicode or ASCII symbols regardless of color detection. |
|
|
890
|
+
| `CLACK_ESCAPE_TIMEOUT=250` | Escape-key detection window in milliseconds, for slow SSH links where arrow keys get misread as Escape. |
|
|
891
|
+
|
|
892
|
+
```bash
|
|
893
|
+
FORCE_COLOR=0 ruby my_cli.rb # plain text in a real terminal
|
|
894
|
+
FORCE_COLOR=1 ruby my_cli.rb 2>&1 | tee run.log # keep colors in a captured log
|
|
895
|
+
```
|
|
896
|
+
|
|
897
|
+
### Guide rail
|
|
898
|
+
|
|
899
|
+
Every prompt and message helper except `progress` draws the gray rail (`│`, `┌`, `└`) that ties a session together. The rail next to the step you are answering is cyan; it turns yellow on a validation error. Turn the rail off globally or per call:
|
|
900
|
+
|
|
901
|
+
```ruby
|
|
902
|
+
Clack.update_settings(with_guide: false) # everywhere
|
|
903
|
+
Clack.select(message: "DB?", options: dbs, with_guide: true) # per-call override wins
|
|
904
|
+
Clack.log.info("plain", with_guide: false)
|
|
905
|
+
```
|
|
906
|
+
|
|
907
|
+
With guides off the connector lines disappear and content starts flush left:
|
|
908
|
+
|
|
909
|
+
```
|
|
910
|
+
◆ Choose a database instead of │
|
|
911
|
+
● PostgreSQL ◆ Choose a database
|
|
912
|
+
○ MySQL │ ● PostgreSQL
|
|
913
|
+
↑/↓ to navigate • Enter: confirm │ ○ MySQL
|
|
914
|
+
│ ↑/↓ to navigate • Enter: confirm
|
|
915
|
+
└
|
|
916
|
+
```
|
|
917
|
+
|
|
918
|
+
`Clack.log.*` and `Clack.stream.*` keep their level symbol (`●`, `▲`, `■`) with guides off; only the rail goes away.
|
|
919
|
+
|
|
920
|
+
### Keyboard hints
|
|
921
|
+
|
|
922
|
+
`select`, `multiselect`, `group_multiselect`, `autocomplete`, `autocomplete_multiselect`, and `path` end with a line of keyboard hints (key names dimmed). Hide it per prompt with `show_instructions: false`, everywhere with `Clack.update_settings(show_instructions: false)`, or replace the text (for localized CLIs) with `instructions:`, a String or an Array of Strings joined with ` • `:
|
|
923
|
+
|
|
924
|
+
```ruby
|
|
925
|
+
Clack.multiselect(
|
|
926
|
+
message: "Funktionen",
|
|
927
|
+
options: features,
|
|
928
|
+
instructions: ["Leertaste: wählen", "a: alle", "i: umkehren", "Enter: bestätigen"]
|
|
929
|
+
)
|
|
930
|
+
|
|
931
|
+
# instructions: works on any prompt, even ones without built-in hints
|
|
932
|
+
Clack.text(message: "Slug?", instructions: "lowercase, digits and dashes")
|
|
933
|
+
```
|
|
700
934
|
|
|
701
935
|
---
|
|
702
936
|
|
|
@@ -735,6 +969,39 @@ The `PromptDriver` yielded to the block provides these methods:
|
|
|
735
969
|
| `ctrl_d` | Press Ctrl+D (submit multiline text) |
|
|
736
970
|
| `key(sym_or_char)` | Press an arbitrary key by symbol (e.g. `:escape`) or raw character |
|
|
737
971
|
|
|
972
|
+
Any object that responds to `getc` works as `input:`, so a plain `StringIO` is enough for simple cases. When the input runs out (EOF) the prompt cancels and returns `Clack::CANCEL`, so end scripted input with `"\r"` to submit (or `"\u0004"`, Ctrl+D, for `multiline_text`):
|
|
973
|
+
|
|
974
|
+
```ruby
|
|
975
|
+
Clack.select(message: "Pick", options: %w[a b c], input: StringIO.new("j\r"), output: StringIO.new)
|
|
976
|
+
# => "b"
|
|
977
|
+
```
|
|
978
|
+
|
|
979
|
+
Arrow keys do not work in a `StringIO`: it cannot be probed for follow-up bytes, so an escape sequence such as `"\e[B"` is read as a bare Escape (which cancels the prompt) followed by `[` and `B`. Use the vim keys (`j`/`k`/`h`/`l`) in scripted input, or `Clack::Testing.simulate` / `Clack::Testing::KeyQueue`, which deliver whole key codes, for arrow navigation.
|
|
980
|
+
|
|
981
|
+
If your app sets `ci_mode: :auto`, reset it in test setup (`Clack::Core::Settings.reset!`): a simulated input is not a TTY, so `:auto` would auto-submit instead of following your script.
|
|
982
|
+
|
|
983
|
+
---
|
|
984
|
+
|
|
985
|
+
## Troubleshooting
|
|
986
|
+
|
|
987
|
+
### `Clack::NotATerminalError: stdin is not an interactive terminal`
|
|
988
|
+
|
|
989
|
+
Prompts read keystrokes in raw mode, which needs a real terminal. You will see this when stdin is a pipe or a file (`echo y | ruby app.rb`, `ruby app.rb < answers.txt`, a cron job, a CI step). Either run the script from a terminal, or opt in to non-interactive runs so prompts submit their defaults:
|
|
990
|
+
|
|
991
|
+
```ruby
|
|
992
|
+
Clack.update_settings(ci_mode: :auto) # auto-submit defaults when input is not a TTY or a CI env var is set
|
|
993
|
+
```
|
|
994
|
+
|
|
995
|
+
To answer prompts from a script rather than skip them, pass the answers as `input:` (see [Testing](#testing)). The error is an `IOError`, so a top-level `rescue IOError` catches it.
|
|
996
|
+
|
|
997
|
+
### Prompt exits with `CANCEL` as soon as piped input runs out
|
|
998
|
+
|
|
999
|
+
End of input is treated like Ctrl+C. Terminate each answer with `"\r"` (Enter), and for `multiline_text` finish with `"\u0004"` (Ctrl+D).
|
|
1000
|
+
|
|
1001
|
+
### Arrow keys type letters, or do nothing, in tmux or after vim
|
|
1002
|
+
|
|
1003
|
+
Terminals in application cursor mode send `ESC O A` instead of `ESC [ A` for Up. Clack 0.7.0 understands both, and also folds the various Home/End encodings into one. If arrows still misbehave over a slow SSH or mosh link, raise the Escape detection window: `CLACK_ESCAPE_TIMEOUT=250` (milliseconds, default 50).
|
|
1004
|
+
|
|
738
1005
|
---
|
|
739
1006
|
|
|
740
1007
|
## Try It
|
|
@@ -149,18 +149,27 @@ Clack.log.info "Language: #{lang}"
|
|
|
149
149
|
# email = prompt.ask("Email?", validate: /\A[\w+\-.]+@[a-z\d\-]+(\.[a-z]+)*\z/i)
|
|
150
150
|
# age = prompt.ask("Age?") { |q| q.validate(->(v) { v.to_i > 0 }, "Must be positive") }
|
|
151
151
|
#
|
|
152
|
-
# Clack
|
|
152
|
+
# Clack takes the same Regexp directly (or :email for the common case).
|
|
153
|
+
# A Hash gives the pattern a custom message. Lambdas return nil (pass)
|
|
154
|
+
# or an error string:
|
|
153
155
|
|
|
154
156
|
email = Clack.text(
|
|
155
157
|
message: "Email?",
|
|
156
|
-
validate:
|
|
157
|
-
"Invalid email" unless v.match?(/\A[\w+\-.]+@[a-z\d-]+(\.[a-z]+)*\z/i)
|
|
158
|
-
}
|
|
158
|
+
validate: {/\A[\w+\-.]+@[a-z\d-]+(\.[a-z]+)*\z/i => "Invalid email"}
|
|
159
159
|
)
|
|
160
160
|
exit 0 if Clack.cancel?(email)
|
|
161
161
|
|
|
162
162
|
Clack.log.info "Email: #{email}"
|
|
163
163
|
|
|
164
|
+
age = Clack.text(
|
|
165
|
+
message: "Age?",
|
|
166
|
+
validate: ->(v) { "Must be positive" unless v.to_i.positive? },
|
|
167
|
+
transform: :to_integer
|
|
168
|
+
)
|
|
169
|
+
exit 0 if Clack.cancel?(age)
|
|
170
|
+
|
|
171
|
+
Clack.log.info "Age: #{age}"
|
|
172
|
+
|
|
164
173
|
# ─────────────────────────────────────────────
|
|
165
174
|
# 8. Collecting multiple prompts (group)
|
|
166
175
|
# ─────────────────────────────────────────────
|
data/lib/clack/box.rb
CHANGED
|
@@ -13,7 +13,10 @@ module Clack
|
|
|
13
13
|
# @param title_padding [Integer] Padding around title
|
|
14
14
|
# @param content_padding [Integer] Padding around content
|
|
15
15
|
# @param rounded [Boolean] Use rounded corners (default: true)
|
|
16
|
-
# @param format_border [Proc] Optional proc to format border characters
|
|
16
|
+
# @param format_border [Proc] Optional proc to format border characters (the guide
|
|
17
|
+
# gutter in front of each line is not passed through it)
|
|
18
|
+
# @param with_guide [Boolean, nil] Prefix every line with the gray guide rail so the box
|
|
19
|
+
# lines up with the prompts around it (default: Clack.settings[:with_guide])
|
|
17
20
|
# @param output [IO] Output stream
|
|
18
21
|
def render(
|
|
19
22
|
message = "",
|
|
@@ -25,12 +28,15 @@ module Clack
|
|
|
25
28
|
content_padding: 2,
|
|
26
29
|
rounded: true,
|
|
27
30
|
format_border: nil,
|
|
31
|
+
with_guide: nil,
|
|
28
32
|
output: $stdout
|
|
29
33
|
)
|
|
30
34
|
ctx = build_context(message, title, title_padding, content_padding, width, rounded, format_border)
|
|
31
|
-
|
|
35
|
+
ctx[:line_prefix] = Core::Settings.with_guide?(with_guide) ? "#{Colors.gray(Symbols::S_BAR)} " : ""
|
|
36
|
+
top = build_top_border(ctx[:display_title], ctx[:inner_width], title_padding, title_align, ctx[:symbols], ctx[:h_symbol])
|
|
37
|
+
output.puts "#{ctx[:line_prefix]}#{top}"
|
|
32
38
|
render_content_lines(output, ctx, content_align, content_padding)
|
|
33
|
-
output.puts "#{ctx[:symbols][2]}#{ctx[:h_symbol] * ctx[:inner_width]}#{ctx[:symbols][3]}"
|
|
39
|
+
output.puts "#{ctx[:line_prefix]}#{ctx[:symbols][2]}#{ctx[:h_symbol] * ctx[:inner_width]}#{ctx[:symbols][3]}"
|
|
34
40
|
end
|
|
35
41
|
|
|
36
42
|
private
|
|
@@ -57,7 +63,7 @@ module Clack
|
|
|
57
63
|
def render_content_lines(output, ctx, content_align, content_padding)
|
|
58
64
|
ctx[:lines].each do |line|
|
|
59
65
|
left_pad, right_pad = padding_for_line(Clack::Utils.visible_length(line), ctx[:inner_width], content_padding, content_align)
|
|
60
|
-
output.puts "#{ctx[:v_symbol]}#{" " * left_pad}#{line}#{" " * right_pad}#{ctx[:v_symbol]}"
|
|
66
|
+
output.puts "#{ctx[:line_prefix]}#{ctx[:v_symbol]}#{" " * left_pad}#{line}#{" " * right_pad}#{ctx[:v_symbol]}"
|
|
61
67
|
end
|
|
62
68
|
end
|
|
63
69
|
|
|
@@ -83,7 +89,7 @@ module Clack
|
|
|
83
89
|
return width + 2 if width.is_a?(Integer) # Add 2 for borders
|
|
84
90
|
|
|
85
91
|
# Auto width: fit to content using display width
|
|
86
|
-
max_line = lines.map { |
|
|
92
|
+
max_line = lines.map { |line| Clack::Utils.visible_length(line) }.max || 0
|
|
87
93
|
title_with_padding = title_len + (title_padding * 2)
|
|
88
94
|
content_with_padding = max_line + (content_padding * 2)
|
|
89
95
|
|
data/lib/clack/colors.rb
CHANGED
|
@@ -2,12 +2,17 @@
|
|
|
2
2
|
|
|
3
3
|
module Clack
|
|
4
4
|
# ANSI color codes for terminal output styling.
|
|
5
|
-
#
|
|
6
|
-
#
|
|
7
|
-
#
|
|
8
|
-
# -
|
|
5
|
+
#
|
|
6
|
+
# Colors are enabled when {Environment.colors_supported?} says so: on a TTY
|
|
7
|
+
# that is not +TERM=dumb+, or anywhere when +FORCE_COLOR+ is set to a
|
|
8
|
+
# non-empty value other than +0+ / +false+. A non-empty +NO_COLOR+ or
|
|
9
|
+
# +FORCE_COLOR=0+ disables them everywhere. When disabled every method
|
|
10
|
+
# returns the plain text unchanged.
|
|
9
11
|
module Colors
|
|
10
12
|
class << self
|
|
13
|
+
# Whether ANSI styling is currently active for +$stdout+.
|
|
14
|
+
# Delegates to {Environment.colors_supported?} on every call.
|
|
15
|
+
# @return [Boolean]
|
|
11
16
|
def enabled? = Environment.colors_supported?
|
|
12
17
|
|
|
13
18
|
# @!group Foreground Colors (standard)
|