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.
data/lib/clack.rb CHANGED
@@ -1,12 +1,14 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require_relative "clack/version"
4
+ require_relative "clack/errors"
4
5
  require_relative "clack/environment"
5
6
  require_relative "clack/symbols"
6
7
  require_relative "clack/colors"
7
8
  require_relative "clack/utils"
8
9
  require_relative "clack/core/cursor"
9
10
  require_relative "clack/core/settings"
11
+ require_relative "clack/core/chrome"
10
12
  require_relative "clack/core/key_reader"
11
13
  require_relative "clack/core/prompt"
12
14
  require_relative "clack/core/options_helper"
@@ -101,7 +103,9 @@ module Clack
101
103
  # Useful for guard clauses in CLI scripts.
102
104
  #
103
105
  # @param value [Object] the result from a prompt
104
- # @param message [String] message to display if cancelled
106
+ # @param message [String, nil] message to display if cancelled (default: the global
107
+ # messages[:cancel], "Cancelled"; see {#update_settings})
108
+ # @param with_guide [Boolean, nil] show the guide symbol (default: Clack.settings[:with_guide])
105
109
  # @param output [IO] output stream
106
110
  # @return [Boolean] true if cancelled
107
111
  #
@@ -111,41 +115,53 @@ module Clack
111
115
  #
112
116
  # @example With custom message
113
117
  # return if Clack.handle_cancel(name, "Aborted by user")
114
- def handle_cancel(value, message = "Cancelled", output: $stdout)
118
+ def handle_cancel(value, message = nil, with_guide: nil, output: $stdout)
115
119
  return false unless cancel?(value)
116
120
 
117
- cancel(message, output: output)
121
+ cancel(message || Core::Settings.message(:cancel), with_guide:, output:)
118
122
  true
119
123
  end
120
124
 
121
125
  # Display an intro banner at the start of a CLI session.
122
126
  #
123
127
  # @param title [String, nil] optional title text
128
+ # @param with_guide [Boolean, nil] show the guide symbol (default: Clack.settings[:with_guide])
124
129
  # @param output [IO] output stream (default: $stdout)
125
130
  # @return [void]
126
- def intro(title = nil, output: $stdout)
127
- output.puts "#{Colors.gray(Symbols::S_BAR_START)} #{title}"
131
+ def intro(title = nil, with_guide: nil, output: $stdout)
132
+ prefix = Core::Settings.with_guide?(with_guide) ? "#{Colors.gray(Symbols::S_BAR_START)} " : ""
133
+ output.puts "#{prefix}#{title}"
128
134
  end
129
135
 
130
136
  # Display an outro banner at the end of a CLI session.
131
137
  #
132
138
  # @param message [String, nil] optional closing message
139
+ # @param with_guide [Boolean, nil] show the guide symbol (default: Clack.settings[:with_guide])
133
140
  # @param output [IO] output stream (default: $stdout)
134
141
  # @return [void]
135
- def outro(message = nil, output: $stdout)
136
- output.puts Colors.gray(Symbols::S_BAR)
137
- output.puts "#{Colors.gray(Symbols::S_BAR_END)} #{message}"
142
+ def outro(message = nil, with_guide: nil, output: $stdout)
143
+ if Core::Settings.with_guide?(with_guide)
144
+ output.puts Colors.gray(Symbols::S_BAR)
145
+ output.puts "#{Colors.gray(Symbols::S_BAR_END)} #{message}"
146
+ else
147
+ output.puts message.to_s
148
+ end
138
149
  output.puts
139
150
  end
140
151
 
141
152
  # Display a cancellation message (typically after user presses Escape).
142
153
  #
143
154
  # @param message [String, nil] optional cancellation message
155
+ # @param with_guide [Boolean, nil] show the guide symbol (default: Clack.settings[:with_guide])
144
156
  # @param output [IO] output stream (default: $stdout)
145
157
  # @return [void]
146
- def cancel(message = nil, output: $stdout)
147
- output.puts Colors.gray(Symbols::S_BAR)
148
- output.puts "#{Colors.gray(Symbols::S_BAR_END)} #{Colors.red(message)}"
158
+ def cancel(message = nil, with_guide: nil, output: $stdout)
159
+ if Core::Settings.with_guide?(with_guide)
160
+ output.puts Colors.gray(Symbols::S_BAR)
161
+ output.puts "#{Colors.gray(Symbols::S_BAR_END)} #{Colors.red(message)}"
162
+ else
163
+ output.puts Colors.red(message)
164
+ end
149
165
  output.puts
150
166
  end
151
167
 
@@ -156,9 +172,12 @@ module Clack
156
172
  # @option opts [String, nil] :default_value value used if submitted empty
157
173
  # @option opts [String, nil] :initial_value pre-filled editable text
158
174
  # @option opts [Array<String>, Proc, nil] :completions tab completion candidates (array or proc)
159
- # @option opts [Proc, nil] :validate validation function returning error string, Warning, or nil
175
+ # @option opts [Proc, Regexp, Symbol, Array, Hash, nil, false] :validate validator (see {Validators.resolve});
176
+ # a proc returns an error string, Warning, or nil; nil or false disables validation
160
177
  # @option opts [Symbol, Proc, nil] :transform transform function to normalize the value
161
178
  # @option opts [String, nil] :help help text shown below the message
179
+ # @option opts [Boolean, nil] :with_guide show the guide rail (default: Clack.settings[:with_guide])
180
+ # @option opts [String, Array<String>, nil] :instructions custom keyboard hint footer text, rendered verbatim on one line (newlines are not re-prefixed)
162
181
  # @return [String, CANCEL] user input or CANCEL if cancelled
163
182
  def text(message:, **opts)
164
183
  Prompts::Text.new(message:, **opts).run
@@ -171,8 +190,11 @@ module Clack
171
190
  #
172
191
  # @param message [String] the prompt message
173
192
  # @option opts [String, nil] :initial_value pre-filled editable text (can contain newlines)
174
- # @option opts [Proc, nil] :validate validation function returning error string, Warning, or nil
193
+ # @option opts [Proc, Regexp, Symbol, Array, Hash, nil, false] :validate validator (see {Validators.resolve});
194
+ # a proc returns an error string, Warning, or nil; nil or false disables validation
175
195
  # @option opts [String, nil] :help help text shown below the message
196
+ # @option opts [Boolean, nil] :with_guide show the guide rail (default: Clack.settings[:with_guide])
197
+ # @option opts [String, Array<String>, nil] :instructions custom keyboard hint footer text, rendered verbatim on one line (newlines are not re-prefixed)
176
198
  # @return [String, CANCEL] user input (lines joined with \n) or CANCEL if cancelled
177
199
  def multiline_text(message:, **opts)
178
200
  Prompts::MultilineText.new(message:, **opts).run
@@ -182,8 +204,11 @@ module Clack
182
204
  #
183
205
  # @param message [String] the prompt message
184
206
  # @option opts [String] :mask character to display for each input character (default: ▪)
185
- # @option opts [Proc, nil] :validate validation function returning error string, Warning, or nil
207
+ # @option opts [Proc, Regexp, Symbol, Array, Hash, nil, false] :validate validator (see {Validators.resolve});
208
+ # a proc returns an error string, Warning, or nil; nil or false disables validation
186
209
  # @option opts [String, nil] :help help text shown below the message
210
+ # @option opts [Boolean, nil] :with_guide show the guide rail (default: Clack.settings[:with_guide])
211
+ # @option opts [String, Array<String>, nil] :instructions custom keyboard hint footer text, rendered verbatim on one line (newlines are not re-prefixed)
187
212
  # @return [String, CANCEL] password or CANCEL if cancelled
188
213
  def password(message:, **opts)
189
214
  Prompts::Password.new(message:, **opts).run
@@ -194,8 +219,22 @@ module Clack
194
219
  # @param message [String] the prompt message
195
220
  # @option opts [String] :active label for "yes" option (default: "Yes")
196
221
  # @option opts [String] :inactive label for "no" option (default: "No")
197
- # @option opts [Boolean] :initial_value default selection (default: true)
222
+ # @option opts [Object] :initial_value default selection (default: true); coerced to a
223
+ # Boolean, so nil and false start on "no" and any other value starts on "yes"
224
+ # @option opts [Boolean] :vertical render the two options on separate lines instead of
225
+ # side by side (default: false); handy for long or localized labels
226
+ # @option opts [String, nil] :help help text shown below the message
227
+ # @option opts [Boolean, nil] :with_guide show the guide rail (default: Clack.settings[:with_guide])
228
+ # @option opts [String, Array<String>, nil] :instructions custom keyboard hint footer text, rendered verbatim on one line (newlines are not re-prefixed)
198
229
  # @return [Boolean, CANCEL] true/false or CANCEL if cancelled
230
+ #
231
+ # @example Stacked layout
232
+ # Clack.confirm(
233
+ # message: "Overwrite ~/.zshrc?",
234
+ # active: "Yes, back it up and replace it",
235
+ # inactive: "No, keep my existing file",
236
+ # vertical: true
237
+ # )
199
238
  def confirm(message:, **opts)
200
239
  Prompts::Confirm.new(message:, **opts).run
201
240
  end
@@ -203,10 +242,18 @@ module Clack
203
242
  # Prompt to select one option from a list.
204
243
  #
205
244
  # @param message [String] the prompt message
206
- # @param options [Array<Hash, String>] list of options
245
+ # @param options [Array<Hash, String>, Hash] list of options, or a Hash of value => label
246
+ # (or value => {label:, hint:, disabled:}); see README "Option shorthands"
207
247
  # @option opts [Object, nil] :initial_value value of initially selected option
208
248
  # @option opts [Integer, nil] :max_items max visible items (enables scrolling)
249
+ # @option opts [Boolean, nil] :with_guide show the guide rail (default: Clack.settings[:with_guide])
250
+ # @option opts [Boolean, nil] :show_instructions show the keyboard hint footer (default: Clack.settings[:show_instructions])
251
+ # @option opts [String, Array<String>, nil] :instructions custom keyboard hint footer text, rendered verbatim on one line (newlines are not re-prefixed)
209
252
  # @return [Object, CANCEL] selected value or CANCEL if cancelled
253
+ #
254
+ # @example Hash shorthand
255
+ # db = Clack.select(message: "Database?", options: {pg: "PostgreSQL", mysql: "MySQL"})
256
+ # # => :pg
210
257
  def select(message:, options:, **opts)
211
258
  Prompts::Select.new(message:, options: options, **opts).run
212
259
  end
@@ -214,11 +261,15 @@ module Clack
214
261
  # Prompt to select multiple options from a list.
215
262
  #
216
263
  # @param message [String] the prompt message
217
- # @param options [Array<Hash, String>] list of options
264
+ # @param options [Array<Hash, String>, Hash] list of options, or a Hash of value => label
265
+ # (or value => {label:, hint:, disabled:})
218
266
  # @option opts [Array, nil] :initial_values initially selected values
219
267
  # @option opts [Boolean] :required require at least one selection (default: true)
220
268
  # @option opts [Integer, nil] :max_items max visible items (enables scrolling)
221
269
  # @option opts [Object, nil] :cursor_at value of initially focused option
270
+ # @option opts [Boolean, nil] :with_guide show the guide rail (default: Clack.settings[:with_guide])
271
+ # @option opts [Boolean, nil] :show_instructions show the keyboard hint footer (default: Clack.settings[:show_instructions])
272
+ # @option opts [String, Array<String>, nil] :instructions custom keyboard hint footer text, rendered verbatim on one line (newlines are not re-prefixed)
222
273
  # @return [Array, CANCEL] selected values or CANCEL if cancelled
223
274
  def multiselect(message:, options:, **opts)
224
275
  Prompts::Multiselect.new(message:, options: options, **opts).run
@@ -230,17 +281,42 @@ module Clack
230
281
  # @option opts [Array<String>, nil] :frames custom animation frames
231
282
  # @option opts [Float, nil] :delay seconds between frames
232
283
  # @option opts [Proc, nil] :style_frame proc to style each frame
284
+ # @option opts [String, nil] :cancel_message text for #cancel with no argument and for
285
+ # Ctrl+C / exit while running (default: the global messages[:cancel], "Cancelled")
286
+ # @option opts [String, nil] :error_message text for #error with no argument and for an
287
+ # uncaught exception while running (default: the global messages[:error],
288
+ # "Something went wrong")
289
+ # @option opts [#call, nil] :on_cancel called with no arguments after the spinner is
290
+ # cancelled, whether by #cancel, Ctrl+C, exit, or an early exit from a Clack.spin
291
+ # block; a StandardError raised by the hook is reported with Kernel#warn and swallowed
233
292
  # @option opts [IO] :output output stream (default: $stdout)
293
+ # @option opts [Boolean, nil] :with_guide show the guide rail (default: Clack.settings[:with_guide])
234
294
  # @return [Prompts::Spinner] spinner instance (call #start, #stop, #error, #cancel, #clear)
295
+ # @raise [ArgumentError] if cancel_message/error_message are not Strings or on_cancel
296
+ # does not respond to #call
297
+ #
298
+ # @example Custom cancel and error messages
299
+ # s = Clack.spinner(cancel_message: "Deploy aborted", error_message: "Deploy failed",
300
+ # on_cancel: -> { release_lock })
301
+ # s.start("Deploying")
235
302
  def spinner(**opts)
236
303
  Prompts::Spinner.new(**opts)
237
304
  end
238
305
 
239
306
  # Run a block with a spinner, handling success/error automatically.
240
307
  #
308
+ # If the block does not complete, the spinner still ends with a final line before the
309
+ # exception or non-local exit propagates: the cancel message for exit (any status),
310
+ # Ctrl+C, break, and throw; +error+ or the exception message for anything raised.
311
+ #
241
312
  # @param message [String] initial spinner message
242
313
  # @param success [String, nil] message on success (defaults to message)
243
314
  # @param error [String, nil] message on error (defaults to exception message)
315
+ # @option opts [String, nil] :cancel_message see {#spinner}
316
+ # @option opts [String, nil] :error_message see {#spinner}; note that an exception
317
+ # raised by the block uses +error:+ or the exception message, not this
318
+ # @option opts [#call, nil] :on_cancel see {#spinner}
319
+ # @option opts [Boolean, nil] :with_guide see {#spinner}
244
320
  # @return [Object] the block's return value
245
321
  # @raise [Exception] re-raises any exception from the block
246
322
  #
@@ -257,6 +333,10 @@ module Clack
257
333
  # s.message "Step 2..."
258
334
  # do_step_2
259
335
  # end
336
+ #
337
+ # @example Early exit still prints a final line
338
+ # Clack.spin("Building", cancel_message: "Build cancelled") { exit 2 }
339
+ # # => "■ Build cancelled", then the process exits with status 2
260
340
  def spin(message, success: nil, error: nil, **opts)
261
341
  s = spinner(**opts)
262
342
  s.start(message)
@@ -264,21 +344,31 @@ module Clack
264
344
  result = yield(s)
265
345
  s.stop(success || message)
266
346
  result
267
- rescue => exception
347
+ rescue SystemExit, SignalException
348
+ s.cancel
349
+ raise
350
+ rescue Exception => exception # standard:disable Lint/RescueException
268
351
  s.error(error || exception.message)
269
352
  raise
353
+ ensure
354
+ # break or throw out of the block leave no exception in flight; a
355
+ # no-op when one of the clauses above already finished the spinner.
356
+ s.abandon
270
357
  end
271
358
  end
272
359
 
273
360
  # Prompt with type-to-filter autocomplete.
274
361
  #
275
362
  # @param message [String] the prompt message
276
- # @param options [Array<Hash, String>] list of options to filter
363
+ # @param options [Array<Hash, String>, Hash] list of options to filter, or a Hash of value => label
277
364
  # @option opts [String, nil] :placeholder placeholder text
278
365
  # @option opts [Proc, nil] :filter custom filter proc receiving (option_hash, query_string)
279
366
  # and returning true/false. Defaults to fuzzy matching across label, value, and hint,
280
367
  # sorted by relevance score.
281
368
  # @option opts [Integer, nil] :max_items max visible items (enables scrolling, default: 5)
369
+ # @option opts [Boolean, nil] :with_guide show the guide rail (default: Clack.settings[:with_guide])
370
+ # @option opts [Boolean, nil] :show_instructions show the keyboard hint footer (default: Clack.settings[:show_instructions])
371
+ # @option opts [String, Array<String>, nil] :instructions custom keyboard hint footer text, rendered verbatim on one line (newlines are not re-prefixed)
282
372
  # @return [Object, CANCEL] selected value or CANCEL if cancelled
283
373
  def autocomplete(message:, options:, **opts)
284
374
  Prompts::Autocomplete.new(message:, options: options, **opts).run
@@ -287,7 +377,7 @@ module Clack
287
377
  # Prompt with type-to-filter autocomplete and multiselect.
288
378
  #
289
379
  # @param message [String] the prompt message
290
- # @param options [Array<Hash, String>] list of options to filter
380
+ # @param options [Array<Hash, String>, Hash] list of options to filter, or a Hash of value => label
291
381
  # @option opts [String, nil] :placeholder placeholder text
292
382
  # @option opts [Boolean] :required require at least one selection (default: true)
293
383
  # @option opts [Array, nil] :initial_values initially selected values
@@ -295,6 +385,9 @@ module Clack
295
385
  # and returning true/false. Defaults to fuzzy matching across label, value, and hint,
296
386
  # sorted by relevance score.
297
387
  # @option opts [Integer, nil] :max_items max visible items (enables scrolling, default: 5)
388
+ # @option opts [Boolean, nil] :with_guide show the guide rail (default: Clack.settings[:with_guide])
389
+ # @option opts [Boolean, nil] :show_instructions show the keyboard hint footer (default: Clack.settings[:show_instructions])
390
+ # @option opts [String, Array<String>, nil] :instructions custom keyboard hint footer text, rendered verbatim on one line (newlines are not re-prefixed)
298
391
  # @return [Array, CANCEL] selected values or CANCEL if cancelled
299
392
  def autocomplete_multiselect(message:, options:, **opts)
300
393
  Prompts::AutocompleteMultiselect.new(message:, options: options, **opts).run
@@ -306,6 +399,11 @@ module Clack
306
399
  # @option opts [String] :root starting directory (default: ".")
307
400
  # @option opts [Boolean] :only_directories only show directories (default: false)
308
401
  # @option opts [Integer, nil] :max_items max visible items (enables scrolling, default: 5)
402
+ # @option opts [Proc, Regexp, Symbol, Array, Hash, nil, false] :validate validator for the resolved
403
+ # absolute path (see {Validators.resolve}); +:directory_exists+ and +:path_exists+ fit here
404
+ # @option opts [Boolean, nil] :with_guide show the guide rail (default: Clack.settings[:with_guide])
405
+ # @option opts [Boolean, nil] :show_instructions show the keyboard hint footer (default: Clack.settings[:show_instructions])
406
+ # @option opts [String, Array<String>, nil] :instructions custom keyboard hint footer text, rendered verbatim on one line (newlines are not re-prefixed)
309
407
  # @return [String, CANCEL] selected path or CANCEL if cancelled
310
408
  def path(message:, **opts)
311
409
  Prompts::Path.new(message:, **opts).run
@@ -322,9 +420,43 @@ module Clack
322
420
 
323
421
  # Prompt to select an option by pressing a key.
324
422
  #
423
+ # Each option carries a single-character +:key+ (defaults to the first
424
+ # character of the value). Pressing that key selects the option and
425
+ # submits immediately, so there is no cursor to move. Enter only does
426
+ # something when +:initial_value+ highlights a default. Option keys win
427
+ # over custom key aliases (except aliases mapped to +:cancel+).
428
+ #
325
429
  # @param message [String] the prompt message
326
- # @param options [Array<Hash>] options with :value, :label, and :key
430
+ # @param options [Array<Hash>, Hash] options with :value, :label, and optionally :key and :hint, or a Hash of
431
+ # value => label (key defaults to the first character of the value) or value => {label:, key:, hint:}
432
+ # @option opts [Boolean] :case_sensitive match keys exactly instead of ignoring case (default: false)
433
+ # @option opts [Object, nil] :initial_value value of the option highlighted at start; Enter submits it
434
+ # and CI mode returns it (default: nil). The highlight is a color effect, so when colors are off
435
+ # (NO_COLOR, piped output) mark the default in that option's :hint. A value that matches no option
436
+ # is ignored with a warning on stderr
437
+ # @option opts [Proc, Regexp, Symbol, Array, Hash, nil, false] :validate validator (see {Validators.resolve});
438
+ # a proc returns an error string, Warning, or nil; nil or false disables validation
439
+ # @option opts [Symbol, Proc, nil] :transform transform function to normalize the value
440
+ # @option opts [String, nil] :help help text shown below the message
441
+ # @option opts [Boolean, nil] :with_guide show the guide rail (default: Clack.settings[:with_guide])
442
+ # @option opts [String, Array<String>, nil] :instructions custom keyboard hint footer text, rendered verbatim on one line (newlines are not re-prefixed)
327
443
  # @return [Object, CANCEL] selected value or CANCEL if cancelled
444
+ #
445
+ # @example Distinct upper- and lowercase keys with a safe default
446
+ # Clack.select_key(
447
+ # message: "Apply migration?",
448
+ # options: [
449
+ # { value: :yes_all, label: "Yes to all", key: "Y" },
450
+ # { value: :yes, label: "Yes", key: "y" },
451
+ # { value: :no, label: "No", key: "n", hint: "default" }
452
+ # ],
453
+ # case_sensitive: true,
454
+ # initial_value: :no
455
+ # )
456
+ #
457
+ # @example Hash shorthand
458
+ # Clack.select_key(message: "Action?", options: {create: "Create", open: "Open", quit: "Quit"})
459
+ # # press "o" => :open
328
460
  def select_key(message:, options:, **opts)
329
461
  Prompts::SelectKey.new(message:, options: options, **opts).run
330
462
  end
@@ -332,6 +464,7 @@ module Clack
332
464
  # Run multiple tasks with progress indicators.
333
465
  #
334
466
  # @param tasks [Array<Hash>] tasks with :title, :task (Proc), and optional :enabled (Boolean, default: true)
467
+ # @option opts [Boolean, nil] :with_guide show the guide rail (default: Clack.settings[:with_guide])
335
468
  # @return [Array<Hash>] task results
336
469
  def tasks(tasks:, **opts)
337
470
  Prompts::Tasks.new(tasks: tasks, **opts).run
@@ -340,12 +473,16 @@ module Clack
340
473
  # Prompt to select multiple options organized in groups.
341
474
  #
342
475
  # @param message [String] the prompt message
343
- # @param options [Array<Hash>] groups with :label and :options
476
+ # @param options [Array<Hash>, Hash] groups with :label and :options, or a Hash of
477
+ # group label => options (each options list accepts an Array or a value => label Hash)
344
478
  # @option opts [Array, nil] :initial_values initially selected values
345
479
  # @option opts [Boolean] :required require at least one selection (default: true)
346
480
  # @option opts [Object, nil] :cursor_at value of initially focused option
347
481
  # @option opts [Boolean] :selectable_groups allow toggling entire groups (default: false)
348
482
  # @option opts [Integer] :group_spacing lines between groups (default: 0)
483
+ # @option opts [Boolean, nil] :with_guide show the guide rail (default: Clack.settings[:with_guide])
484
+ # @option opts [Boolean, nil] :show_instructions show the keyboard hint footer (default: Clack.settings[:show_instructions])
485
+ # @option opts [String, Array<String>, nil] :instructions custom keyboard hint footer text, rendered verbatim on one line (newlines are not re-prefixed)
349
486
  # @return [Array, CANCEL] selected values or CANCEL if cancelled
350
487
  def group_multiselect(message:, options:, **opts)
351
488
  Prompts::GroupMultiselect.new(message:, options: options, **opts).run
@@ -361,8 +498,11 @@ module Clack
361
498
  # @option opts [Date, Time, String, nil] :initial_value initial date value (default: today)
362
499
  # @option opts [Date, nil] :min minimum allowed date
363
500
  # @option opts [Date, nil] :max maximum allowed date
364
- # @option opts [Proc, nil] :validate custom validation proc
501
+ # @option opts [Proc, Regexp, Symbol, Array, Hash, nil, false] :validate validator (see {Validators.resolve});
502
+ # a proc returns an error string, Warning, or nil; nil or false disables validation
365
503
  # @option opts [String, nil] :help help text shown below the message
504
+ # @option opts [Boolean, nil] :with_guide show the guide rail (default: Clack.settings[:with_guide])
505
+ # @option opts [String, Array<String>, nil] :instructions custom keyboard hint footer text, rendered verbatim on one line (newlines are not re-prefixed)
366
506
  # @return [Date, CANCEL] selected date or CANCEL if cancelled
367
507
  def date(message:, **opts)
368
508
  Prompts::Date.new(message:, **opts).run
@@ -375,14 +515,24 @@ module Clack
375
515
  # @param message [String] the prompt message
376
516
  # @option opts [Numeric] :min minimum value (default: 0)
377
517
  # @option opts [Numeric] :max maximum value (default: 100)
378
- # @option opts [Numeric] :step increment size (default: 1)
518
+ # @option opts [Numeric] :step increment size (default: 1); fractional steps
519
+ # such as 0.1 are snapped exactly, so three steps from 0 is 0.3
379
520
  # @option opts [Numeric, nil] :initial_value initial value (defaults to min)
380
- # @option opts [Proc, nil] :validate validation proc
521
+ # @option opts [Proc, Regexp, Symbol, Array, Hash, nil, false] :validate validator (see {Validators.resolve});
522
+ # a proc returns an error string, Warning, or nil; nil or false disables validation
381
523
  # @option opts [String, nil] :help help text shown below the message
382
- # @return [Numeric, CANCEL] selected value or CANCEL if cancelled
524
+ # @option opts [Boolean, nil] :with_guide show the guide rail (default: Clack.settings[:with_guide])
525
+ # @option opts [String, Array<String>, nil] :instructions custom keyboard hint footer text, rendered verbatim on one line (newlines are not re-prefixed)
526
+ # @return [Numeric, CANCEL] the selected value (Integer when min and
527
+ # step are both Integers, Float when either is a Float; Rational inputs
528
+ # return Rational) or CANCEL if cancelled
383
529
  #
384
530
  # @example Basic usage
385
531
  # volume = Clack.range(message: "Volume", min: 0, max: 100, step: 5)
532
+ #
533
+ # @example Fractional step
534
+ # opacity = Clack.range(message: "Opacity", min: 0, max: 1, step: 0.1)
535
+ # # => 0.3 after three right arrows
386
536
  def range(message:, **opts)
387
537
  Prompts::Range.new(message:, **opts).run
388
538
  end
@@ -405,6 +555,7 @@ module Clack
405
555
  #
406
556
  # @param message [String] the note content
407
557
  # @param title [String, nil] optional title
558
+ # @option opts [Boolean, nil] :with_guide show the guide rail (default: Clack.settings[:with_guide])
408
559
  # @return [void]
409
560
  def note(message = "", title: nil, **opts)
410
561
  Note.render(message, title: title, **opts)
@@ -418,6 +569,7 @@ module Clack
418
569
  # @option opts [:left, :center, :right] :title_align title alignment
419
570
  # @option opts [Integer, :auto] :width box width
420
571
  # @option opts [Boolean] :rounded use rounded corners
572
+ # @option opts [Boolean, nil] :with_guide show the guide rail (default: Clack.settings[:with_guide])
421
573
  # @return [void]
422
574
  def box(message = "", title: "", **opts)
423
575
  Box.render(message, title: title, **opts)
@@ -429,13 +581,14 @@ module Clack
429
581
  # @param title [String] title displayed at the top
430
582
  # @option opts [Integer, nil] :limit max lines to show (older lines scroll out)
431
583
  # @option opts [Boolean] :retain_log keep full log history for display on error
584
+ # @option opts [Boolean, nil] :with_guide show the guide rail (default: Clack.settings[:with_guide])
432
585
  # @return [TaskLog] task log instance
433
586
  def task_log(title:, **opts)
434
587
  TaskLog.new(title: title, **opts)
435
588
  end
436
589
 
437
590
  # Access global settings
438
- # @return [Hash] Current configuration
591
+ # @return [Hash] Current configuration (:aliases, :with_guide, :show_instructions, :ci_mode, :messages)
439
592
  # @see Core::Settings.update for modifying settings
440
593
  def settings
441
594
  Core::Settings.config
@@ -443,21 +596,34 @@ module Clack
443
596
 
444
597
  # Update global settings
445
598
  # @option opts [Hash, nil] :aliases Custom key to action mappings
446
- # @option opts [Boolean, nil] :with_guide Whether to show guide bars
447
- # @option opts [Boolean, Symbol, nil] :ci_mode CI mode: true (always), :auto (detect non-TTY/CI env), false (never)
599
+ # @option opts [Boolean, nil] :with_guide Whether to show guide bars (the gray rail and corners around prompts)
600
+ # @option opts [Boolean, nil] :show_instructions Whether list prompts show their keyboard hint footer
601
+ # @option opts [Boolean, Symbol, nil] :ci_mode CI mode: true (always), :auto (active when
602
+ # the prompt's input is not a TTY, or a CI env var is set), false (never)
603
+ # @option opts [Hash{Symbol=>String}, nil] :messages Cancel/error strings used by spinners
604
+ # and {#handle_cancel}: +{cancel: "Cancelled", error: "Something went wrong"}+.
605
+ # Keys are merged, so either can be given alone.
448
606
  # @return [Hash] Updated configuration
607
+ # @raise [ArgumentError] for an unknown messages key or a non-String value
449
608
  #
450
609
  # @example Custom key bindings
451
610
  # Clack.update_settings(aliases: { "y" => :enter, "n" => :cancel })
452
611
  #
453
- # @example Disable guide bars
612
+ # @example Disable guide bars everywhere (per-call with_guide: overrides this)
454
613
  # Clack.update_settings(with_guide: false)
455
614
  #
615
+ # @example Hide keyboard hint footers
616
+ # Clack.update_settings(show_instructions: false)
617
+ #
456
618
  # @example Enable CI mode (auto-submit with defaults)
457
619
  # Clack.update_settings(ci_mode: true)
458
620
  #
459
- # @example Auto-detect CI mode (non-TTY or CI environment)
621
+ # @example Auto-detect CI mode (piped input or CI environment)
622
+ # # Without this, prompting on a piped stdin raises Clack::NotATerminalError.
460
623
  # Clack.update_settings(ci_mode: :auto)
624
+ #
625
+ # @example Localize cancel and error messages
626
+ # Clack.update_settings(messages: {cancel: "Abgebrochen", error: "Etwas ging schief"})
461
627
  def update_settings(**opts)
462
628
  Core::Settings.update(**opts)
463
629
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: clack
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.6.2
4
+ version: 0.7.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Steve Whittaker
@@ -43,6 +43,7 @@ files:
43
43
  - lib/clack.rb
44
44
  - lib/clack/box.rb
45
45
  - lib/clack/colors.rb
46
+ - lib/clack/core/chrome.rb
46
47
  - lib/clack/core/ci_mode.rb
47
48
  - lib/clack/core/cursor.rb
48
49
  - lib/clack/core/fuzzy_matcher.rb
@@ -54,6 +55,7 @@ files:
54
55
  - lib/clack/core/settings.rb
55
56
  - lib/clack/core/text_input_helper.rb
56
57
  - lib/clack/environment.rb
58
+ - lib/clack/errors.rb
57
59
  - lib/clack/group.rb
58
60
  - lib/clack/log.rb
59
61
  - lib/clack/note.rb