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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +76 -1
- data/README.md +184 -10
- data/examples/advanced_prompts.rb +63 -0
- data/examples/basic.rb +15 -0
- data/examples/create_app.rb +86 -0
- data/examples/date_demo.rb +40 -0
- data/examples/demo.rb +179 -0
- data/examples/full_demo.rb +84 -0
- data/examples/group_demo.rb +79 -0
- data/examples/images/confirm_example.rb +12 -0
- data/examples/images/multiselect_example.rb +15 -0
- data/examples/images/password_example.rb +10 -0
- data/examples/images/select_example.rb +15 -0
- data/examples/images/spinner_example.rb +11 -0
- data/examples/images/text_example.rb +11 -0
- data/examples/spinner_demo.rb +38 -0
- data/examples/tasks_demo.rb +59 -0
- data/examples/validation.rb +73 -0
- data/lib/clack/colors.rb +97 -3
- data/lib/clack/core/ci_mode.rb +35 -0
- data/lib/clack/core/cursor.rb +1 -0
- data/lib/clack/core/fuzzy_matcher.rb +121 -0
- data/lib/clack/core/key_reader.rb +5 -0
- data/lib/clack/core/prompt.rb +98 -20
- data/lib/clack/core/scroll_helper.rb +54 -0
- data/lib/clack/core/settings.rb +11 -3
- data/lib/clack/core/text_input_helper.rb +28 -8
- data/lib/clack/environment.rb +1 -1
- data/lib/clack/log.rb +51 -0
- data/lib/clack/note.rb +7 -0
- data/lib/clack/prompts/autocomplete.rb +27 -34
- data/lib/clack/prompts/autocomplete_multiselect.rb +23 -66
- data/lib/clack/prompts/date.rb +280 -0
- data/lib/clack/prompts/group_multiselect.rb +46 -18
- data/lib/clack/prompts/multiline_text.rb +8 -9
- data/lib/clack/prompts/multiselect.rb +3 -5
- data/lib/clack/prompts/password.rb +5 -10
- data/lib/clack/prompts/path.rb +24 -27
- data/lib/clack/prompts/progress.rb +2 -6
- data/lib/clack/prompts/range.rb +112 -0
- data/lib/clack/prompts/select.rb +2 -6
- data/lib/clack/prompts/select_key.rb +5 -8
- data/lib/clack/prompts/spinner.rb +12 -10
- data/lib/clack/prompts/tasks.rb +47 -62
- data/lib/clack/prompts/text.rb +61 -5
- data/lib/clack/stream.rb +32 -3
- data/lib/clack/symbols.rb +25 -0
- data/lib/clack/task_log.rb +3 -5
- data/lib/clack/testing.rb +171 -0
- data/lib/clack/transformers.rb +8 -7
- data/lib/clack/validators.rb +33 -2
- data/lib/clack/version.rb +2 -1
- data/lib/clack.rb +123 -215
- metadata +23 -1
data/lib/clack/validators.rb
CHANGED
|
@@ -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
|
|
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
|
-
|
|
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
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
|
-
# @
|
|
146
|
-
# @
|
|
147
|
-
# @
|
|
148
|
-
# @
|
|
149
|
-
# @
|
|
150
|
-
# @
|
|
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
|
-
# @
|
|
163
|
-
# @
|
|
164
|
-
# @
|
|
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
|
-
# @
|
|
174
|
-
# @
|
|
175
|
-
# @
|
|
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
|
-
# @
|
|
185
|
-
# @
|
|
186
|
-
# @
|
|
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
|
-
# @
|
|
197
|
-
# @
|
|
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
|
-
# @
|
|
208
|
-
# @
|
|
209
|
-
# @
|
|
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
|
-
# @
|
|
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
|
|
226
|
-
# @param
|
|
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
|
-
# @
|
|
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
|
-
# @
|
|
271
|
-
# @
|
|
272
|
-
# @
|
|
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
|
-
# @
|
|
282
|
-
# @
|
|
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
|
-
# @
|
|
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
|
|
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
|
-
# @
|
|
319
|
-
# @
|
|
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
|
-
# @
|
|
352
|
-
# @
|
|
353
|
-
# @
|
|
354
|
-
# @
|
|
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
|
-
# @
|
|
365
|
-
# @
|
|
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
|
-
# @
|
|
380
|
-
# @
|
|
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
|
-
#
|
|
428
|
-
#
|
|
429
|
-
#
|
|
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
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
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
|
+
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
|