clack 0.1.4 → 0.4.1

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.
Files changed (55) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +76 -1
  3. data/README.md +184 -10
  4. data/examples/advanced_prompts.rb +63 -0
  5. data/examples/basic.rb +15 -0
  6. data/examples/create_app.rb +86 -0
  7. data/examples/date_demo.rb +40 -0
  8. data/examples/demo.rb +179 -0
  9. data/examples/full_demo.rb +84 -0
  10. data/examples/group_demo.rb +79 -0
  11. data/examples/images/confirm_example.rb +12 -0
  12. data/examples/images/multiselect_example.rb +15 -0
  13. data/examples/images/password_example.rb +10 -0
  14. data/examples/images/select_example.rb +15 -0
  15. data/examples/images/spinner_example.rb +11 -0
  16. data/examples/images/text_example.rb +11 -0
  17. data/examples/spinner_demo.rb +38 -0
  18. data/examples/tasks_demo.rb +59 -0
  19. data/examples/validation.rb +73 -0
  20. data/lib/clack/colors.rb +97 -3
  21. data/lib/clack/core/ci_mode.rb +35 -0
  22. data/lib/clack/core/cursor.rb +1 -0
  23. data/lib/clack/core/fuzzy_matcher.rb +121 -0
  24. data/lib/clack/core/key_reader.rb +5 -0
  25. data/lib/clack/core/prompt.rb +98 -20
  26. data/lib/clack/core/scroll_helper.rb +54 -0
  27. data/lib/clack/core/settings.rb +11 -3
  28. data/lib/clack/core/text_input_helper.rb +28 -8
  29. data/lib/clack/environment.rb +1 -1
  30. data/lib/clack/log.rb +51 -0
  31. data/lib/clack/note.rb +7 -0
  32. data/lib/clack/prompts/autocomplete.rb +27 -34
  33. data/lib/clack/prompts/autocomplete_multiselect.rb +23 -66
  34. data/lib/clack/prompts/date.rb +280 -0
  35. data/lib/clack/prompts/group_multiselect.rb +46 -18
  36. data/lib/clack/prompts/multiline_text.rb +8 -9
  37. data/lib/clack/prompts/multiselect.rb +3 -5
  38. data/lib/clack/prompts/password.rb +5 -10
  39. data/lib/clack/prompts/path.rb +24 -27
  40. data/lib/clack/prompts/progress.rb +2 -6
  41. data/lib/clack/prompts/range.rb +112 -0
  42. data/lib/clack/prompts/select.rb +2 -6
  43. data/lib/clack/prompts/select_key.rb +5 -8
  44. data/lib/clack/prompts/spinner.rb +12 -10
  45. data/lib/clack/prompts/tasks.rb +47 -62
  46. data/lib/clack/prompts/text.rb +61 -5
  47. data/lib/clack/stream.rb +32 -3
  48. data/lib/clack/symbols.rb +25 -0
  49. data/lib/clack/task_log.rb +3 -5
  50. data/lib/clack/testing.rb +171 -0
  51. data/lib/clack/transformers.rb +8 -7
  52. data/lib/clack/validators.rb +33 -2
  53. data/lib/clack/version.rb +2 -1
  54. data/lib/clack.rb +123 -215
  55. metadata +23 -1
@@ -44,7 +44,7 @@ module Clack
44
44
  # Validates minimum length.
45
45
  #
46
46
  # @param length [Integer] Minimum length
47
- # @param message [String, nil] Custom error message (supports %d placeholder)
47
+ # @param message [String, nil] Custom error message
48
48
  # @return [Proc] Validator proc
49
49
  def min_length(length, message = nil)
50
50
  msg = message || "Must be at least #{length} characters"
@@ -142,6 +142,35 @@ module Clack
142
142
  ->(value) { message unless File.directory?(value.to_s) }
143
143
  end
144
144
 
145
+ # Validates that the date is strictly after today.
146
+ # Today itself is not considered "future" and will fail validation.
147
+ #
148
+ # @param message [String] Error message
149
+ # @return [Proc] Validator proc
150
+ def future_date(message = "Date must be in the future")
151
+ ->(date) { message if date <= Date.today }
152
+ end
153
+
154
+ # Validates that the date is strictly before today.
155
+ # Today itself is not considered "past" and will fail validation.
156
+ #
157
+ # @param message [String] Error message
158
+ # @return [Proc] Validator proc
159
+ def past_date(message = "Date must be in the past")
160
+ ->(date) { message if date >= Date.today }
161
+ end
162
+
163
+ # Validates that the date is within a given range.
164
+ #
165
+ # @param min [Date] Minimum date
166
+ # @param max [Date] Maximum date
167
+ # @param message [String, nil] Custom error message
168
+ # @return [Proc] Validator proc
169
+ def date_range(min:, max:, message: nil)
170
+ msg = message || "Date must be between #{min} and #{max}"
171
+ ->(date) { msg unless (min..max).cover?(date) }
172
+ end
173
+
145
174
  # Warning if file exists. Allows user to confirm overwrite.
146
175
  #
147
176
  # @param message [String] Warning message
@@ -170,7 +199,9 @@ module Clack
170
199
  def as_warning(validator)
171
200
  lambda do |value|
172
201
  result = validator.call(value)
173
- Clack::Warning.new(result) if result
202
+ next if result.nil?
203
+
204
+ result.is_a?(Clack::Warning) ? result : Clack::Warning.new(result)
174
205
  end
175
206
  end
176
207
 
data/lib/clack/version.rb CHANGED
@@ -1,5 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Clack
4
- VERSION = "0.1.4"
4
+ # Current gem version.
5
+ VERSION = "0.4.1"
5
6
  end
data/lib/clack.rb CHANGED
@@ -11,6 +11,9 @@ require_relative "clack/core/key_reader"
11
11
  require_relative "clack/core/prompt"
12
12
  require_relative "clack/core/options_helper"
13
13
  require_relative "clack/core/text_input_helper"
14
+ require_relative "clack/core/scroll_helper"
15
+ require_relative "clack/core/fuzzy_matcher"
16
+ require_relative "clack/core/ci_mode"
14
17
  require_relative "clack/prompts/text"
15
18
  require_relative "clack/prompts/multiline_text"
16
19
  require_relative "clack/prompts/password"
@@ -25,6 +28,8 @@ require_relative "clack/prompts/progress"
25
28
  require_relative "clack/prompts/select_key"
26
29
  require_relative "clack/prompts/tasks"
27
30
  require_relative "clack/prompts/group_multiselect"
31
+ require_relative "clack/prompts/date"
32
+ require_relative "clack/prompts/range"
28
33
  require_relative "clack/log"
29
34
  require_relative "clack/note"
30
35
  require_relative "clack/box"
@@ -63,6 +68,7 @@ module Clack
63
68
  # @example Validator returning a warning
64
69
  # validate: ->(v) { Clack::Warning.new("File exists, overwrite?") if File.exist?(v) }
65
70
  Warning = Data.define(:message) do
71
+ # @return [String] the warning message
66
72
  def to_s = message
67
73
  end
68
74
 
@@ -85,6 +91,9 @@ module Clack
85
91
  def cancel?(value)
86
92
  value.equal?(CANCEL)
87
93
  end
94
+ # @!method cancelled?(value)
95
+ # Alias for {#cancel?} (British spelling).
96
+ # @see #cancel?
88
97
  alias_method :cancelled?, :cancel?
89
98
 
90
99
  # Check if cancelled and show cancel message if so.
@@ -142,12 +151,13 @@ module Clack
142
151
  # Prompt for single-line text input.
143
152
  #
144
153
  # @param message [String] the prompt message
145
- # @param placeholder [String, nil] dim text shown when input is empty
146
- # @param default_value [String, nil] value used if submitted empty
147
- # @param initial_value [String, nil] pre-filled editable text
148
- # @param validate [Proc, nil] validation function returning error string, Warning, or nil
149
- # @param transform [Symbol, Proc, nil] transform function to normalize the value
150
- # @param help [String, nil] help text shown below the message
154
+ # @option opts [String, nil] :placeholder dim text shown when input is empty
155
+ # @option opts [String, nil] :default_value value used if submitted empty
156
+ # @option opts [String, nil] :initial_value pre-filled editable text
157
+ # @option opts [Array<String>, Proc, nil] :completions tab completion candidates (array or proc)
158
+ # @option opts [Proc, nil] :validate validation function returning error string, Warning, or nil
159
+ # @option opts [Symbol, Proc, nil] :transform transform function to normalize the value
160
+ # @option opts [String, nil] :help help text shown below the message
151
161
  # @return [String, CANCEL] user input or CANCEL if cancelled
152
162
  def text(message:, **opts)
153
163
  Prompts::Text.new(message:, **opts).run
@@ -159,9 +169,9 @@ module Clack
159
169
  # notes, or any multi-line content.
160
170
  #
161
171
  # @param message [String] the prompt message
162
- # @param initial_value [String, nil] pre-filled editable text (can contain newlines)
163
- # @param validate [Proc, nil] validation function returning error string, Warning, or nil
164
- # @param help [String, nil] help text shown below the message
172
+ # @option opts [String, nil] :initial_value pre-filled editable text (can contain newlines)
173
+ # @option opts [Proc, nil] :validate validation function returning error string, Warning, or nil
174
+ # @option opts [String, nil] :help help text shown below the message
165
175
  # @return [String, CANCEL] user input (lines joined with \n) or CANCEL if cancelled
166
176
  def multiline_text(message:, **opts)
167
177
  Prompts::MultilineText.new(message:, **opts).run
@@ -170,9 +180,9 @@ module Clack
170
180
  # Prompt for password input (masked display).
171
181
  #
172
182
  # @param message [String] the prompt message
173
- # @param mask [String] character to display for each input character (default: ▪)
174
- # @param validate [Proc, nil] validation function returning error string, Warning, or nil
175
- # @param help [String, nil] help text shown below the message
183
+ # @option opts [String] :mask character to display for each input character (default: ▪)
184
+ # @option opts [Proc, nil] :validate validation function returning error string, Warning, or nil
185
+ # @option opts [String, nil] :help help text shown below the message
176
186
  # @return [String, CANCEL] password or CANCEL if cancelled
177
187
  def password(message:, **opts)
178
188
  Prompts::Password.new(message:, **opts).run
@@ -181,9 +191,9 @@ module Clack
181
191
  # Prompt for yes/no confirmation.
182
192
  #
183
193
  # @param message [String] the prompt message
184
- # @param active [String] label for "yes" option (default: "Yes")
185
- # @param inactive [String] label for "no" option (default: "No")
186
- # @param initial_value [Boolean] default selection (default: true)
194
+ # @option opts [String] :active label for "yes" option (default: "Yes")
195
+ # @option opts [String] :inactive label for "no" option (default: "No")
196
+ # @option opts [Boolean] :initial_value default selection (default: true)
187
197
  # @return [Boolean, CANCEL] true/false or CANCEL if cancelled
188
198
  def confirm(message:, **opts)
189
199
  Prompts::Confirm.new(message:, **opts).run
@@ -193,8 +203,8 @@ module Clack
193
203
  #
194
204
  # @param message [String] the prompt message
195
205
  # @param options [Array<Hash, String>] list of options
196
- # @param initial_value [Object, nil] value of initially selected option
197
- # @param max_items [Integer, nil] max visible items (enables scrolling)
206
+ # @option opts [Object, nil] :initial_value value of initially selected option
207
+ # @option opts [Integer, nil] :max_items max visible items (enables scrolling)
198
208
  # @return [Object, CANCEL] selected value or CANCEL if cancelled
199
209
  def select(message:, options:, **opts)
200
210
  Prompts::Select.new(message:, options: options, **opts).run
@@ -204,9 +214,10 @@ module Clack
204
214
  #
205
215
  # @param message [String] the prompt message
206
216
  # @param options [Array<Hash, String>] list of options
207
- # @param initial_values [Array, nil] initially selected values
208
- # @param required [Boolean] require at least one selection (default: true)
209
- # @param max_items [Integer, nil] max visible items (enables scrolling)
217
+ # @option opts [Array, nil] :initial_values initially selected values
218
+ # @option opts [Boolean] :required require at least one selection (default: true)
219
+ # @option opts [Integer, nil] :max_items max visible items (enables scrolling)
220
+ # @option opts [Object, nil] :cursor_at value of initially focused option
210
221
  # @return [Array, CANCEL] selected values or CANCEL if cancelled
211
222
  def multiselect(message:, options:, **opts)
212
223
  Prompts::Multiselect.new(message:, options: options, **opts).run
@@ -214,7 +225,12 @@ module Clack
214
225
 
215
226
  # Create an animated spinner for async operations.
216
227
  #
217
- # @return [Prompts::Spinner] spinner instance (call #start, #stop, #error)
228
+ # @option opts [:dots, :timer] :indicator animation style (default: :dots)
229
+ # @option opts [Array<String>, nil] :frames custom animation frames
230
+ # @option opts [Float, nil] :delay seconds between frames
231
+ # @option opts [Proc, nil] :style_frame proc to style each frame
232
+ # @option opts [IO] :output output stream (default: $stdout)
233
+ # @return [Prompts::Spinner] spinner instance (call #start, #stop, #error, #cancel, #clear)
218
234
  def spinner(**opts)
219
235
  Prompts::Spinner.new(**opts)
220
236
  end
@@ -222,8 +238,8 @@ module Clack
222
238
  # Run a block with a spinner, handling success/error automatically.
223
239
  #
224
240
  # @param message [String] initial spinner message
225
- # @param success_message [String, nil] message on success (defaults to message)
226
- # @param error_message [String, nil] message on error (defaults to exception message)
241
+ # @param success [String, nil] message on success (defaults to message)
242
+ # @param error [String, nil] message on error (defaults to exception message)
227
243
  # @return [Object] the block's return value
228
244
  # @raise [Exception] re-raises any exception from the block
229
245
  #
@@ -257,7 +273,11 @@ module Clack
257
273
  #
258
274
  # @param message [String] the prompt message
259
275
  # @param options [Array<Hash, String>] list of options to filter
260
- # @param placeholder [String, nil] placeholder text
276
+ # @option opts [String, nil] :placeholder placeholder text
277
+ # @option opts [Proc, nil] :filter custom filter proc receiving (option_hash, query_string)
278
+ # and returning true/false. Defaults to fuzzy matching across label, value, and hint,
279
+ # sorted by relevance score.
280
+ # @option opts [Integer, nil] :max_items max visible items (enables scrolling, default: 5)
261
281
  # @return [Object, CANCEL] selected value or CANCEL if cancelled
262
282
  def autocomplete(message:, options:, **opts)
263
283
  Prompts::Autocomplete.new(message:, options: options, **opts).run
@@ -267,9 +287,13 @@ module Clack
267
287
  #
268
288
  # @param message [String] the prompt message
269
289
  # @param options [Array<Hash, String>] list of options to filter
270
- # @param placeholder [String, nil] placeholder text
271
- # @param required [Boolean] require at least one selection (default: true)
272
- # @param initial_values [Array, nil] initially selected values
290
+ # @option opts [String, nil] :placeholder placeholder text
291
+ # @option opts [Boolean] :required require at least one selection (default: true)
292
+ # @option opts [Array, nil] :initial_values initially selected values
293
+ # @option opts [Proc, nil] :filter custom filter proc receiving (option_hash, query_string)
294
+ # and returning true/false. Defaults to fuzzy matching across label, value, and hint,
295
+ # sorted by relevance score.
296
+ # @option opts [Integer, nil] :max_items max visible items (enables scrolling, default: 5)
273
297
  # @return [Array, CANCEL] selected values or CANCEL if cancelled
274
298
  def autocomplete_multiselect(message:, options:, **opts)
275
299
  Prompts::AutocompleteMultiselect.new(message:, options: options, **opts).run
@@ -278,8 +302,9 @@ module Clack
278
302
  # Prompt for file/directory path with filesystem navigation.
279
303
  #
280
304
  # @param message [String] the prompt message
281
- # @param root [String] starting directory (default: ".")
282
- # @param only_directories [Boolean] only show directories (default: false)
305
+ # @option opts [String] :root starting directory (default: ".")
306
+ # @option opts [Boolean] :only_directories only show directories (default: false)
307
+ # @option opts [Integer, nil] :max_items max visible items (enables scrolling, default: 5)
283
308
  # @return [String, CANCEL] selected path or CANCEL if cancelled
284
309
  def path(message:, **opts)
285
310
  Prompts::Path.new(message:, **opts).run
@@ -288,7 +313,7 @@ module Clack
288
313
  # Create a progress bar for measurable operations.
289
314
  #
290
315
  # @param total [Integer] total number of steps
291
- # @param message [String, nil] optional message
316
+ # @option opts [String, nil] :message optional message
292
317
  # @return [Prompts::Progress] progress instance (call #start, #advance, #stop)
293
318
  def progress(total:, **opts)
294
319
  Prompts::Progress.new(total: total, **opts)
@@ -305,7 +330,7 @@ module Clack
305
330
 
306
331
  # Run multiple tasks with progress indicators.
307
332
  #
308
- # @param tasks [Array<Hash>] tasks with :title and :task (Proc)
333
+ # @param tasks [Array<Hash>] tasks with :title, :task (Proc), and optional :enabled (Boolean, default: true)
309
334
  # @return [Array<Hash>] task results
310
335
  def tasks(tasks:, **opts)
311
336
  Prompts::Tasks.new(tasks: tasks, **opts).run
@@ -315,13 +340,52 @@ module Clack
315
340
  #
316
341
  # @param message [String] the prompt message
317
342
  # @param options [Array<Hash>] groups with :label and :options
318
- # @param initial_values [Array, nil] initially selected values
319
- # @param required [Boolean] require at least one selection (default: true)
343
+ # @option opts [Array, nil] :initial_values initially selected values
344
+ # @option opts [Boolean] :required require at least one selection (default: true)
345
+ # @option opts [Object, nil] :cursor_at value of initially focused option
346
+ # @option opts [Boolean] :selectable_groups allow toggling entire groups (default: true)
347
+ # @option opts [Integer] :group_spacing lines between groups (default: 0)
320
348
  # @return [Array, CANCEL] selected values or CANCEL if cancelled
321
349
  def group_multiselect(message:, options:, **opts)
322
350
  Prompts::GroupMultiselect.new(message:, options: options, **opts).run
323
351
  end
324
352
 
353
+ # Prompt for date selection with inline segmented input.
354
+ #
355
+ # Navigate between segments with Tab/arrow keys, adjust with up/down,
356
+ # or type digits directly.
357
+ #
358
+ # @param message [String] the prompt message
359
+ # @option opts [Symbol] :format date format (:iso, :us, :eu)
360
+ # @option opts [Date, Time, String, nil] :initial_value initial date value (default: today)
361
+ # @option opts [Date, nil] :min minimum allowed date
362
+ # @option opts [Date, nil] :max maximum allowed date
363
+ # @option opts [Proc, nil] :validate custom validation proc
364
+ # @option opts [String, nil] :help help text shown below the message
365
+ # @return [Date, CANCEL] selected date or CANCEL if cancelled
366
+ def date(message:, **opts)
367
+ Prompts::Date.new(message:, **opts).run
368
+ end
369
+
370
+ # Prompt for a numeric value using a slider.
371
+ #
372
+ # Navigate with left/right or up/down arrow keys. Press Enter to confirm.
373
+ #
374
+ # @param message [String] the prompt message
375
+ # @option opts [Numeric] :min minimum value (default: 0)
376
+ # @option opts [Numeric] :max maximum value (default: 100)
377
+ # @option opts [Numeric] :step increment size (default: 1)
378
+ # @option opts [Numeric, nil] :default initial value (defaults to min)
379
+ # @option opts [Proc, nil] :validate validation proc
380
+ # @option opts [String, nil] :help help text shown below the message
381
+ # @return [Numeric, CANCEL] selected value or CANCEL if cancelled
382
+ #
383
+ # @example Basic usage
384
+ # volume = Clack.range(message: "Volume", min: 0, max: 100, step: 5)
385
+ def range(message:, **opts)
386
+ Prompts::Range.new(message:, **opts).run
387
+ end
388
+
325
389
  # Access the Log module for styled console output.
326
390
  #
327
391
  # @return [Module] the Log module
@@ -345,13 +409,14 @@ module Clack
345
409
  Note.render(message, title: title, **opts)
346
410
  end
347
411
 
348
- # Display content in a customizable box
412
+ # Display content in a customizable box.
413
+ #
349
414
  # @param message [String] the box content
350
- # @param title [String] optional title
351
- # @param content_align [:left, :center, :right] content alignment
352
- # @param title_align [:left, :center, :right] title alignment
353
- # @param width [Integer, :auto] box width
354
- # @param rounded [Boolean] use rounded corners
415
+ # @param title [String, nil] optional title
416
+ # @option opts [:left, :center, :right] :content_align content alignment
417
+ # @option opts [:left, :center, :right] :title_align title alignment
418
+ # @option opts [Integer, :auto] :width box width
419
+ # @option opts [Boolean] :rounded use rounded corners
355
420
  # @return [void]
356
421
  def box(message = "", title: "", **opts)
357
422
  Box.render(message, title: title, **opts)
@@ -361,8 +426,8 @@ module Clack
361
426
  # Useful for build output, npm install style streaming, etc.
362
427
  #
363
428
  # @param title [String] title displayed at the top
364
- # @param limit [Integer, nil] max lines to show (older lines scroll out)
365
- # @param retain_log [Boolean] keep full log history for display on error
429
+ # @option opts [Integer, nil] :limit max lines to show (older lines scroll out)
430
+ # @option opts [Boolean] :retain_log keep full log history for display on error
366
431
  # @return [TaskLog] task log instance
367
432
  def task_log(title:, **opts)
368
433
  TaskLog.new(title: title, **opts)
@@ -376,8 +441,9 @@ module Clack
376
441
  end
377
442
 
378
443
  # Update global settings
379
- # @param aliases [Hash, nil] Custom key to action mappings
380
- # @param with_guide [Boolean, nil] Whether to show guide bars
444
+ # @option opts [Hash, nil] :aliases Custom key to action mappings
445
+ # @option opts [Boolean, nil] :with_guide Whether to show guide bars
446
+ # @option opts [Boolean, Symbol, nil] :ci_mode CI mode: true (always), :auto (detect non-TTY/CI env), false (never)
381
447
  # @return [Hash] Updated configuration
382
448
  #
383
449
  # @example Custom key bindings
@@ -385,6 +451,12 @@ module Clack
385
451
  #
386
452
  # @example Disable guide bars
387
453
  # Clack.update_settings(with_guide: false)
454
+ #
455
+ # @example Enable CI mode (auto-submit with defaults)
456
+ # Clack.update_settings(ci_mode: true)
457
+ #
458
+ # @example Auto-detect CI mode (non-TTY or CI environment)
459
+ # Clack.update_settings(ci_mode: :auto)
388
460
  def update_settings(**opts)
389
461
  Core::Settings.update(**opts)
390
462
  end
@@ -424,179 +496,15 @@ module Clack
424
496
  Environment.rows(output, default: default)
425
497
  end
426
498
 
427
- # :nocov:
428
- # :reek:TooManyStatements :reek:NestedIterators :reek:UncommunicativeVariableName
429
- # Demo - showcases all Clack features (interactive, tested manually)
499
+ # Run the interactive demo showcasing all Clack features.
500
+ # The demo implementation is in examples/demo.rb.
501
+ #
502
+ # @return [void]
430
503
  def demo
431
- intro "clack-demo"
432
-
433
- result = group(on_cancel: ->(_) { cancel("Operation cancelled.") }) do |g|
434
- g.prompt(:name) do
435
- text(
436
- message: "What is your project named?",
437
- placeholder: "my-app",
438
- validate: ->(v) { "Project name is required" if v.to_s.strip.empty? }
439
- )
440
- end
441
-
442
- g.prompt(:directory) do |r|
443
- text(
444
- message: "Where should we create your project?",
445
- initial_value: "./#{r[:name]}"
446
- )
447
- end
448
-
449
- g.prompt(:template) do
450
- select(
451
- message: "Which template would you like to use?",
452
- options: [
453
- {value: "default", label: "Default", hint: "recommended"},
454
- {value: "minimal", label: "Minimal", hint: "bare bones"},
455
- {value: "api", label: "API Only", hint: "no frontend"},
456
- {value: "full", label: "Full Stack", hint: "everything included"}
457
- ]
458
- )
459
- end
460
-
461
- g.prompt(:typescript) do
462
- confirm(
463
- message: "Would you like to use TypeScript?",
464
- initial_value: true
465
- )
466
- end
467
-
468
- g.prompt(:features) do
469
- multiselect(
470
- message: "Which features would you like to include?",
471
- options: [
472
- {value: "eslint", label: "ESLint", hint: "code linting"},
473
- {value: "prettier", label: "Prettier", hint: "code formatting"},
474
- {value: "tailwind", label: "Tailwind CSS", hint: "utility-first CSS"},
475
- {value: "docker", label: "Docker", hint: "containerization"},
476
- {value: "ci", label: "GitHub Actions", hint: "CI/CD pipeline"}
477
- ],
478
- initial_values: %w[eslint prettier],
479
- required: false
480
- )
481
- end
482
-
483
- g.prompt(:package_manager) do
484
- select(
485
- message: "Which package manager do you prefer?",
486
- options: [
487
- {value: "npm", label: "npm"},
488
- {value: "yarn", label: "yarn"},
489
- {value: "pnpm", label: "pnpm", hint: "recommended"},
490
- {value: "bun", label: "bun", hint: "fast"}
491
- ],
492
- initial_value: "pnpm"
493
- )
494
- end
495
- end
496
-
497
- return if cancel?(result)
498
-
499
- # Autocomplete prompt
500
- color = autocomplete(
501
- message: "Pick a theme color:",
502
- options: %w[red orange yellow green blue indigo violet pink cyan magenta]
503
- )
504
- return if handle_cancel(color)
505
-
506
- # Select key prompt (quick keyboard shortcuts)
507
- action = select_key(
508
- message: "What would you like to do first?",
509
- options: [
510
- {value: "dev", label: "Start dev server", key: "d"},
511
- {value: "build", label: "Build for production", key: "b"},
512
- {value: "test", label: "Run tests", key: "t"}
513
- ]
514
- )
515
- return if handle_cancel(action)
516
-
517
- # Path prompt
518
- config_path = path(
519
- message: "Select config directory:",
520
- only_directories: true
521
- )
522
- return if handle_cancel(config_path)
523
-
524
- # Group multiselect
525
- stack = group_multiselect(
526
- message: "Select additional integrations:",
527
- options: [
528
- {
529
- label: "Frontend",
530
- options: [
531
- {value: "react", label: "React"},
532
- {value: "vue", label: "Vue"},
533
- {value: "svelte", label: "Svelte"}
534
- ]
535
- },
536
- {
537
- label: "Backend",
538
- options: [
539
- {value: "express", label: "Express"},
540
- {value: "fastify", label: "Fastify"},
541
- {value: "hono", label: "Hono"}
542
- ]
543
- },
544
- {
545
- label: "Database",
546
- options: [
547
- {value: "postgres", label: "PostgreSQL"},
548
- {value: "mysql", label: "MySQL"},
549
- {value: "sqlite", label: "SQLite"}
550
- ]
551
- }
552
- ],
553
- required: false
554
- )
555
- return if handle_cancel(stack)
556
-
557
- # Progress bar
558
- prog = progress(total: 100, message: "Downloading assets...")
559
- prog.start
560
- 20.times do
561
- sleep 0.03
562
- prog.advance(5)
563
- end
564
- prog.stop("Assets downloaded!")
565
-
566
- # Tasks
567
- tasks(tasks: [
568
- {title: "Validating configuration", task: -> { sleep 0.3 }},
569
- {title: "Generating types", task: -> { sleep 0.4 }},
570
- {title: "Compiling assets", task: -> { sleep 0.3 }}
571
- ])
572
-
573
- # Spinner
574
- s = spinner
575
- s.start "Installing dependencies via #{result[:package_manager]}..."
576
- sleep 1.0
577
- s.message "Configuring #{result[:template]} template..."
578
- sleep 0.6
579
- s.stop "Project created successfully!"
580
-
581
- # Summary
582
- log.step "Project: #{result[:name]}"
583
- log.step "Directory: #{result[:directory]}"
584
- log.step "Template: #{result[:template]}"
585
- log.step "TypeScript: #{result[:typescript] ? "Yes" : "No"}"
586
- log.step "Features: #{result[:features].join(", ")}" unless result[:features].empty?
587
- log.step "Color: #{color}"
588
- log.step "Action: #{action}"
589
- log.step "Config: #{config_path}"
590
- log.step "Stack: #{stack.join(", ")}" unless stack.empty?
591
-
592
- note <<~MSG, title: "Next steps"
593
- cd #{result[:directory]}
594
- #{result[:package_manager]} run dev
595
- MSG
596
-
597
- outro "Happy coding!"
598
- end
599
- # :nocov:
504
+ demo_path = File.expand_path("../examples/demo.rb", __dir__)
505
+ load demo_path
506
+ run_demo
507
+ end
600
508
  end
601
509
  end
602
510
 
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.1.4
4
+ version: 0.4.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Steve Whittaker
@@ -21,14 +21,33 @@ files:
21
21
  - CHANGELOG.md
22
22
  - LICENSE
23
23
  - README.md
24
+ - examples/advanced_prompts.rb
25
+ - examples/basic.rb
26
+ - examples/create_app.rb
27
+ - examples/date_demo.rb
28
+ - examples/demo.rb
29
+ - examples/full_demo.rb
30
+ - examples/group_demo.rb
31
+ - examples/images/confirm_example.rb
32
+ - examples/images/multiselect_example.rb
33
+ - examples/images/password_example.rb
34
+ - examples/images/select_example.rb
35
+ - examples/images/spinner_example.rb
36
+ - examples/images/text_example.rb
37
+ - examples/spinner_demo.rb
38
+ - examples/tasks_demo.rb
39
+ - examples/validation.rb
24
40
  - exe/clack-demo
25
41
  - lib/clack.rb
26
42
  - lib/clack/box.rb
27
43
  - lib/clack/colors.rb
44
+ - lib/clack/core/ci_mode.rb
28
45
  - lib/clack/core/cursor.rb
46
+ - lib/clack/core/fuzzy_matcher.rb
29
47
  - lib/clack/core/key_reader.rb
30
48
  - lib/clack/core/options_helper.rb
31
49
  - lib/clack/core/prompt.rb
50
+ - lib/clack/core/scroll_helper.rb
32
51
  - lib/clack/core/settings.rb
33
52
  - lib/clack/core/text_input_helper.rb
34
53
  - lib/clack/environment.rb
@@ -38,12 +57,14 @@ files:
38
57
  - lib/clack/prompts/autocomplete.rb
39
58
  - lib/clack/prompts/autocomplete_multiselect.rb
40
59
  - lib/clack/prompts/confirm.rb
60
+ - lib/clack/prompts/date.rb
41
61
  - lib/clack/prompts/group_multiselect.rb
42
62
  - lib/clack/prompts/multiline_text.rb
43
63
  - lib/clack/prompts/multiselect.rb
44
64
  - lib/clack/prompts/password.rb
45
65
  - lib/clack/prompts/path.rb
46
66
  - lib/clack/prompts/progress.rb
67
+ - lib/clack/prompts/range.rb
47
68
  - lib/clack/prompts/select.rb
48
69
  - lib/clack/prompts/select_key.rb
49
70
  - lib/clack/prompts/spinner.rb
@@ -52,6 +73,7 @@ files:
52
73
  - lib/clack/stream.rb
53
74
  - lib/clack/symbols.rb
54
75
  - lib/clack/task_log.rb
76
+ - lib/clack/testing.rb
55
77
  - lib/clack/transformers.rb
56
78
  - lib/clack/utils.rb
57
79
  - lib/clack/validators.rb