clack 0.1.4 → 0.2.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.
Files changed (49) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +26 -1
  3. data/README.md +108 -8
  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/cursor.rb +1 -0
  22. data/lib/clack/core/key_reader.rb +5 -0
  23. data/lib/clack/core/prompt.rb +52 -8
  24. data/lib/clack/core/settings.rb +4 -0
  25. data/lib/clack/core/text_input_helper.rb +4 -0
  26. data/lib/clack/log.rb +51 -0
  27. data/lib/clack/note.rb +7 -0
  28. data/lib/clack/prompts/autocomplete.rb +29 -11
  29. data/lib/clack/prompts/autocomplete_multiselect.rb +5 -6
  30. data/lib/clack/prompts/date.rb +280 -0
  31. data/lib/clack/prompts/group_multiselect.rb +46 -18
  32. data/lib/clack/prompts/multiline_text.rb +8 -9
  33. data/lib/clack/prompts/multiselect.rb +3 -5
  34. data/lib/clack/prompts/password.rb +5 -10
  35. data/lib/clack/prompts/path.rb +2 -2
  36. data/lib/clack/prompts/progress.rb +2 -6
  37. data/lib/clack/prompts/select.rb +2 -6
  38. data/lib/clack/prompts/select_key.rb +5 -7
  39. data/lib/clack/prompts/spinner.rb +2 -6
  40. data/lib/clack/prompts/tasks.rb +50 -9
  41. data/lib/clack/prompts/text.rb +4 -3
  42. data/lib/clack/stream.rb +32 -3
  43. data/lib/clack/symbols.rb +25 -0
  44. data/lib/clack/task_log.rb +3 -5
  45. data/lib/clack/transformers.rb +8 -7
  46. data/lib/clack/validators.rb +33 -2
  47. data/lib/clack/version.rb +2 -1
  48. data/lib/clack.rb +72 -213
  49. metadata +18 -1
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 671583e7a4d14fd6951369f179ed7ba6baf97c20fb54e202e08e710763448ee4
4
- data.tar.gz: 28469575a56abce1883038147ac2ce5735b79cd976938d448b37227fe044f941
3
+ metadata.gz: 9b2beeffde4af3c88491d8b659dd781e8bd3e652e06f8c159a3ca7910fc69c87
4
+ data.tar.gz: 86b779a202099d27cf702009ee649e19a49b11305e10d2033cf7a0425fe38e10
5
5
  SHA512:
6
- metadata.gz: 0a2d08f4e1eaf1434d7692666fb145ba6bd98c2f368193565a4ed0f4a23bf1b937b9909be4fd16541b7f26f7dc018b2f06b95215104c8121f0af964ea4487193
7
- data.tar.gz: 879876e598e6213239b3aaf945e909587c02fa34fcbf2b971d816a41c21149fefbca1065aca5890e4cbb7148382b4a54262a21a2309dabcc5466cd4e43d13aea
6
+ metadata.gz: 9f8b20f670252b17132fcb664eeeaa3ba06b8a8fa0abbee5fdb8e40d17460f2e59badeda5b11b20aaa82b7e9b4b0fef6d06524933794dd20a3c6fcd159cc2115
7
+ data.tar.gz: 21642c6a8cea0671cff152932e806ea7a83163612ecd35739fa2e2bd98179b5e5b714ae6de3e5a2153206ac5f3b52f02ec2e28fc82541056a726d60071afbdab
data/CHANGELOG.md CHANGED
@@ -1,6 +1,31 @@
1
1
  # Changelog
2
2
 
3
- ## [Unreleased]
3
+ ## [0.2.0] - 2026-02-19
4
+
5
+ ### Added
6
+ - `date` prompt for inline segmented date selection with Tab/arrow navigation and digit typing
7
+ - Date min/max enforcement: clamps values to bounds during navigation
8
+ - Date-specific validators: `Validators.future_date`, `Validators.past_date`, `Validators.date_range`
9
+ - `autocomplete` now accepts `filter:` proc for custom matching logic
10
+ - `tasks` now passes a message-update proc to task callbacks for mid-task status updates
11
+ - `tasks` now supports `enabled:` flag to conditionally skip tasks
12
+ - 100% YARD documentation coverage (was 79%)
13
+ - 30 new edge-case tests covering warning validation, transforms, date boundaries, and more
14
+
15
+ ### Changed
16
+ - `Transformers.resolve` now accepts any object responding to `#call` (not just Proc)
17
+ - Standardized required-validation error messages across multiselect variants
18
+ - Extracted `dispatch_key` from `handle_key` in base `Prompt` so warning/error state transitions are handled centrally for all prompts
19
+ - Ruby idiom improvements: pattern matching, endless methods, guard clauses, `find_index`
20
+ - Gemspec now excludes dev-only files (cast, exp, gif, svg) from the gem package
21
+ - Examples include `require "clack"` alternative comment for gem users
22
+
23
+ ### Fixed
24
+ - `Password#handle_input` now correctly handles backspace (was unreachable due to guard order)
25
+ - `Validators.as_warning` no longer double-wraps values that are already `Warning` instances
26
+ - `AutocompleteMultiselect` backspace was dead code (printable guard blocked it)
27
+ - `MultilineText` and `Autocomplete` now render warning validation messages (was error-only)
28
+ - Removed dead `@buffer` and `@name` instance variables from `TaskLogGroup`
4
29
 
5
30
  ## [0.1.4] - 2026-01-23
6
31
 
data/README.md CHANGED
@@ -95,9 +95,16 @@ agg examples/demo.cast examples/demo.gif --font-size 18 --cols 80 --rows 28 --sp
95
95
  All prompts return the user's input, or `Clack::CANCEL` if they pressed Escape/Ctrl+C.
96
96
 
97
97
  ```ruby
98
- # Always check for cancellation
98
+ # Check for cancellation
99
99
  result = Clack.text(message: "Name?")
100
100
  exit 1 if Clack.cancel?(result)
101
+
102
+ # Or use handle_cancel for a one-liner that prints "Cancelled" and returns true
103
+ result = Clack.text(message: "Name?")
104
+ exit 1 if Clack.handle_cancel(result)
105
+
106
+ # With a custom message
107
+ exit 1 if Clack.handle_cancel(result, "Aborted by user")
101
108
  ```
102
109
 
103
110
  ### Validation & Transforms
@@ -144,15 +151,25 @@ Built-in validators and transformers:
144
151
 
145
152
  ```ruby
146
153
  # Validators - return error message, Warning, or nil
147
- Clack::Validators.required
148
- Clack::Validators.min_length(8)
154
+ Clack::Validators.required # Non-empty input
155
+ Clack::Validators.min_length(3) # Minimum character count
156
+ Clack::Validators.max_length(100) # Maximum character count
149
157
  Clack::Validators.format(/\A[a-z]+\z/, "Only lowercase")
150
- Clack::Validators.email
151
- Clack::Validators.combine(v1, v2) # First error/warning wins
158
+ Clack::Validators.email # Email format (user@host.tld)
159
+ Clack::Validators.url # URL format (http/https)
160
+ Clack::Validators.integer # Integer string ("-5", "42")
161
+ Clack::Validators.in_range(1..100) # Numeric range (parses as int)
162
+ Clack::Validators.one_of(%w[a b c]) # Allowlist check
163
+ Clack::Validators.path_exists # File/dir exists on disk
164
+ Clack::Validators.directory_exists # Directory exists on disk
165
+ Clack::Validators.future_date # Date strictly after today
166
+ Clack::Validators.past_date # Date strictly before today
167
+ Clack::Validators.date_range(min: d1, max: d2) # Date within range
168
+ Clack::Validators.combine(v1, v2) # First error/warning wins
152
169
 
153
170
  # Warning validators - allow user to confirm or edit
154
- Clack::Validators.file_exists_warning # For file overwrite confirmations
155
- Clack::Validators.as_warning(validator) # Convert any validator to warning
171
+ Clack::Validators.file_exists_warning # For file overwrite confirmations
172
+ Clack::Validators.as_warning(validator) # Convert any validator to warning
156
173
 
157
174
  # Transformers - normalize the value (use :symbol or Clack::Transformers.name)
158
175
  :strip / :trim # Remove leading/trailing whitespace
@@ -191,6 +208,18 @@ secret = Clack.password(
191
208
  )
192
209
  ```
193
210
 
211
+ ### Multiline Text
212
+
213
+ Multi-line text input. Enter inserts a newline, Ctrl+D submits.
214
+
215
+ ```ruby
216
+ bio = Clack.multiline_text(
217
+ message: "Tell us about yourself",
218
+ initial_value: "Hello!\n",
219
+ validate: ->(v) { "Too short" if v.strip.length < 10 }
220
+ )
221
+ ```
222
+
194
223
  ### Confirm
195
224
 
196
225
  <img src="examples/images/confirm.svg" alt="Confirm prompt">
@@ -255,6 +284,13 @@ color = Clack.autocomplete(
255
284
  options: %w[red orange yellow green blue indigo violet],
256
285
  placeholder: "Type to search..."
257
286
  )
287
+
288
+ # Custom filter logic (receives option hash and query string)
289
+ cmd = Clack.autocomplete(
290
+ message: "Select command",
291
+ options: commands,
292
+ filter: ->(opt, query) { opt[:label].start_with?(query) }
293
+ )
258
294
  ```
259
295
 
260
296
  ### Autocomplete Multiselect
@@ -287,6 +323,23 @@ project_dir = Clack.path(
287
323
 
288
324
  **Navigation:** Type to filter | `Tab` to autocomplete | `↑↓` to select
289
325
 
326
+ ### Date
327
+
328
+ Segmented date picker with three format modes.
329
+
330
+ ```ruby
331
+ date = Clack.date(
332
+ message: "Release date?",
333
+ format: :us, # :iso (YYYY-MM-DD), :us (MM/DD/YYYY), :eu (DD/MM/YYYY)
334
+ initial_value: Date.today + 7,
335
+ min: Date.today,
336
+ max: Date.today + 365,
337
+ validate: ->(d) { "Not a Friday" unless d.friday? }
338
+ )
339
+ ```
340
+
341
+ **Navigation:** `Tab`/`←→` between segments | `↑↓` adjust value | type digits directly
342
+
290
343
  ### Select Key
291
344
 
292
345
  Quick selection using keyboard shortcuts.
@@ -320,6 +373,23 @@ spinner.stop("Dependencies installed!")
320
373
  # Or: spinner.cancel("Cancelled")
321
374
  ```
322
375
 
376
+ **Block form** - wraps a block with automatic success/error handling:
377
+
378
+ ```ruby
379
+ result = Clack.spin("Installing dependencies...") { system("npm install") }
380
+
381
+ # With custom messages
382
+ Clack.spin("Compiling...", success: "Build complete!") { build_project }
383
+
384
+ # Access the spinner inside the block
385
+ Clack.spin("Working...") do |s|
386
+ s.message "Step 1..."
387
+ do_step_1
388
+ s.message "Step 2..."
389
+ do_step_2
390
+ end
391
+ ```
392
+
323
393
  ### Progress
324
394
 
325
395
  Visual progress bar for measurable operations.
@@ -346,6 +416,22 @@ results = Clack.tasks(tasks: [
346
416
  { title: "Building project", task: -> { build } },
347
417
  { title: "Running tests", task: -> { run_tests } }
348
418
  ])
419
+
420
+ # Update the spinner message mid-task
421
+ Clack.tasks(tasks: [
422
+ { title: "Installing", task: ->(message) {
423
+ message.call("Fetching packages...")
424
+ fetch_packages
425
+ message.call("Compiling...")
426
+ compile
427
+ }}
428
+ ])
429
+
430
+ # Conditionally skip tasks with enabled:
431
+ Clack.tasks(tasks: [
432
+ { title: "Lint", task: -> { lint } },
433
+ { title: "Deploy", task: -> { deploy }, enabled: ENV["DEPLOY"] == "true" }
434
+ ])
349
435
  ```
350
436
 
351
437
  ### Group Multiselect
@@ -370,7 +456,9 @@ features = Clack.group_multiselect(
370
456
  { value: "solid_queue", label: "Solid Queue" }
371
457
  ]
372
458
  }
373
- ]
459
+ ],
460
+ selectable_groups: true, # Toggle all options in a group at once
461
+ group_spacing: 1 # Blank lines between groups
374
462
  )
375
463
  ```
376
464
 
@@ -485,6 +573,18 @@ Clack.outro("Done!") # └ Done!
485
573
  Clack.cancel("Aborted") # └ Aborted (red)
486
574
  ```
487
575
 
576
+ ## Configuration
577
+
578
+ Customize key bindings and display options globally:
579
+
580
+ ```ruby
581
+ # Add custom key bindings (merged with defaults)
582
+ Clack.update_settings(aliases: { "y" => :enter, "n" => :cancel })
583
+
584
+ # Disable guide bars
585
+ Clack.update_settings(with_guide: false)
586
+ ```
587
+
488
588
  ## Requirements
489
589
 
490
590
  - Ruby 3.2+
@@ -0,0 +1,63 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ # Demonstrates advanced prompt types: multiline_text, path, select_key, autocomplete
5
+ # Run with: ruby examples/advanced_prompts.rb
6
+
7
+ require_relative "../lib/clack" # Or: require "clack" if installed as a gem
8
+
9
+ Clack.intro "advanced-prompts"
10
+
11
+ # Multi-line text with initial value (Ctrl+D to submit)
12
+ description = Clack.multiline_text(
13
+ message: "Project description:",
14
+ initial_value: "A Ruby CLI tool that "
15
+ )
16
+ exit 0 if Clack.cancel?(description)
17
+
18
+ Clack.log.info "Description:\n#{description}"
19
+
20
+ # Path selector (directories only, rooted at home)
21
+ project_dir = Clack.path(
22
+ message: "Choose project directory:",
23
+ root: Dir.home,
24
+ only_directories: true
25
+ )
26
+ exit 0 if Clack.cancel?(project_dir)
27
+
28
+ Clack.log.info "Directory: #{project_dir}"
29
+
30
+ # Select by key press (instant selection)
31
+ action = Clack.select_key(
32
+ message: "What next?",
33
+ options: [
34
+ {value: "init", label: "Initialize repo", key: "i", hint: "git init"},
35
+ {value: "clone", label: "Clone existing", key: "c", hint: "git clone"},
36
+ {value: "open", label: "Open in editor", key: "o"},
37
+ {value: "skip", label: "Skip", key: "s"}
38
+ ]
39
+ )
40
+ exit 0 if Clack.cancel?(action)
41
+
42
+ Clack.log.step "Action: #{action}"
43
+
44
+ # Autocomplete with placeholder
45
+ license = Clack.autocomplete(
46
+ message: "Pick a license:",
47
+ placeholder: "Type to search...",
48
+ options: [
49
+ {value: "mit", label: "MIT", hint: "permissive"},
50
+ {value: "apache2", label: "Apache 2.0", hint: "permissive"},
51
+ {value: "gpl3", label: "GPL 3.0", hint: "copyleft"},
52
+ {value: "agpl3", label: "AGPL 3.0", hint: "copyleft"},
53
+ {value: "bsd2", label: "BSD 2-Clause", hint: "permissive"},
54
+ {value: "bsd3", label: "BSD 3-Clause", hint: "permissive"},
55
+ {value: "mpl2", label: "MPL 2.0", hint: "weak copyleft"},
56
+ {value: "unlicense", label: "Unlicense", hint: "public domain"}
57
+ ]
58
+ )
59
+ exit 0 if Clack.cancel?(license)
60
+
61
+ Clack.log.success "License: #{license}"
62
+
63
+ Clack.outro "Done!"
data/examples/basic.rb ADDED
@@ -0,0 +1,15 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ # Run with: ruby examples/basic.rb
5
+
6
+ require_relative "../lib/clack" # Or: require "clack" if installed as a gem
7
+
8
+ Clack.intro "basic-example"
9
+
10
+ name = Clack.text(message: "What is your name?", placeholder: "Anonymous")
11
+ exit 0 if Clack.cancel?(name)
12
+
13
+ Clack.log.success "Hello, #{name}!"
14
+
15
+ Clack.outro "Goodbye!"
@@ -0,0 +1,86 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ # Run with: ruby examples/create_app.rb
5
+
6
+ require_relative "../lib/clack" # Or: require "clack" if installed as a gem
7
+
8
+ Clack.intro "create-my-app"
9
+
10
+ # Project name
11
+ name = Clack.text(
12
+ message: "Project name?",
13
+ placeholder: "my-app",
14
+ validate: ->(v) { "Project name is required" if v.to_s.strip.empty? }
15
+ )
16
+ exit 0 if Clack.cancel?(name)
17
+
18
+ # Framework selection
19
+ framework = Clack.select(
20
+ message: "Pick a framework",
21
+ options: [
22
+ {value: "rails", label: "Ruby on Rails", hint: "full-stack"},
23
+ {value: "sinatra", label: "Sinatra", hint: "micro"},
24
+ {value: "hanami", label: "Hanami"},
25
+ {value: "roda", label: "Roda"}
26
+ ]
27
+ )
28
+ exit 0 if Clack.cancel?(framework)
29
+
30
+ # Feature selection
31
+ features = Clack.multiselect(
32
+ message: "Select features",
33
+ options: [
34
+ {value: "api", label: "API Mode"},
35
+ {value: "auth", label: "Authentication"},
36
+ {value: "admin", label: "Admin Panel"},
37
+ {value: "docker", label: "Docker Setup"},
38
+ {value: "ci", label: "GitHub Actions CI"}
39
+ ],
40
+ required: false
41
+ )
42
+ exit 0 if Clack.cancel?(features)
43
+
44
+ # Database
45
+ database = Clack.select(
46
+ message: "Select database",
47
+ options: [
48
+ {value: "postgresql", label: "PostgreSQL", hint: "recommended"},
49
+ {value: "mysql", label: "MySQL"},
50
+ {value: "sqlite", label: "SQLite"}
51
+ ]
52
+ )
53
+ exit 0 if Clack.cancel?(database)
54
+
55
+ # Confirm
56
+ proceed = Clack.confirm(message: "Create project?")
57
+ exit 0 if Clack.cancel?(proceed)
58
+
59
+ unless proceed
60
+ Clack.outro "Project creation cancelled."
61
+ exit 0
62
+ end
63
+
64
+ # Simulate installation
65
+ s = Clack.spinner
66
+ s.start "Creating project..."
67
+ sleep 1
68
+ s.message "Installing dependencies..."
69
+ sleep 1
70
+ s.message "Configuring #{framework}..."
71
+ sleep 0.5
72
+ s.stop "Project created!"
73
+
74
+ # Summary
75
+ Clack.log.info "Project: #{name}"
76
+ Clack.log.success "Framework: #{framework}"
77
+ Clack.log.step "Database: #{database}"
78
+ Clack.log.step "Features: #{features.join(", ")}" unless features.empty?
79
+
80
+ Clack.note <<~NOTE, title: "Next Steps"
81
+ cd #{name}
82
+ bundle install
83
+ bin/rails server
84
+ NOTE
85
+
86
+ Clack.outro "Happy coding!"
@@ -0,0 +1,40 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ # Run with: ruby examples/date_demo.rb
5
+
6
+ require_relative "../lib/clack" # Or: require "clack" if installed as a gem
7
+
8
+ Clack.intro "date-picker-demo"
9
+
10
+ # Basic date selection
11
+ date = Clack.date(
12
+ message: "When should this deploy?",
13
+ help: "Use Tab/arrows to navigate, Up/Down to adjust, or type digits"
14
+ )
15
+ exit 0 if Clack.cancel?(date)
16
+
17
+ Clack.log.info "Selected: #{date}"
18
+
19
+ # Date with bounds
20
+ bounded_date = Clack.date(
21
+ message: "Pick a date this year",
22
+ format: :us,
23
+ initial_value: Date.today,
24
+ min: Date.new(Date.today.year, 1, 1),
25
+ max: Date.new(Date.today.year, 12, 31)
26
+ )
27
+ exit 0 if Clack.cancel?(bounded_date)
28
+
29
+ Clack.log.info "Bounded date: #{bounded_date}"
30
+
31
+ # Date with custom validation
32
+ future_date = Clack.date(
33
+ message: "Schedule for future date",
34
+ validate: Clack::Validators.future_date
35
+ )
36
+ exit 0 if Clack.cancel?(future_date)
37
+
38
+ Clack.log.success "Scheduled for #{future_date}"
39
+
40
+ Clack.outro "Done!"
data/examples/demo.rb ADDED
@@ -0,0 +1,179 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ # Demo script showcasing all Clack Ruby prompts
5
+ # Run with: ruby examples/demo.rb
6
+
7
+ require_relative "../lib/clack" # Or: require "clack" if installed as a gem
8
+
9
+ def run_demo
10
+ Clack.intro "clack-demo"
11
+
12
+ result = Clack.group(on_cancel: ->(_) { Clack.cancel("Operation cancelled.") }) do |g|
13
+ g.prompt(:name) do
14
+ Clack.text(
15
+ message: "What is your project named?",
16
+ placeholder: "my-app",
17
+ validate: ->(v) { "Project name is required" if v.to_s.strip.empty? }
18
+ )
19
+ end
20
+
21
+ g.prompt(:directory) do |r|
22
+ Clack.text(
23
+ message: "Where should we create your project?",
24
+ initial_value: "./#{r[:name]}"
25
+ )
26
+ end
27
+
28
+ g.prompt(:template) do
29
+ Clack.select(
30
+ message: "Which template would you like to use?",
31
+ options: [
32
+ {value: "default", label: "Default", hint: "recommended"},
33
+ {value: "minimal", label: "Minimal", hint: "bare bones"},
34
+ {value: "api", label: "API Only", hint: "no frontend"},
35
+ {value: "full", label: "Full Stack", hint: "everything included"}
36
+ ]
37
+ )
38
+ end
39
+
40
+ g.prompt(:typescript) do
41
+ Clack.confirm(
42
+ message: "Would you like to use TypeScript?",
43
+ initial_value: true
44
+ )
45
+ end
46
+
47
+ g.prompt(:features) do
48
+ Clack.multiselect(
49
+ message: "Which features would you like to include?",
50
+ options: [
51
+ {value: "eslint", label: "ESLint", hint: "code linting"},
52
+ {value: "prettier", label: "Prettier", hint: "code formatting"},
53
+ {value: "tailwind", label: "Tailwind CSS", hint: "utility-first CSS"},
54
+ {value: "docker", label: "Docker", hint: "containerization"},
55
+ {value: "ci", label: "GitHub Actions", hint: "CI/CD pipeline"}
56
+ ],
57
+ initial_values: %w[eslint prettier],
58
+ required: false
59
+ )
60
+ end
61
+
62
+ g.prompt(:package_manager) do
63
+ Clack.select(
64
+ message: "Which package manager do you prefer?",
65
+ options: [
66
+ {value: "npm", label: "npm"},
67
+ {value: "yarn", label: "yarn"},
68
+ {value: "pnpm", label: "pnpm", hint: "recommended"},
69
+ {value: "bun", label: "bun", hint: "fast"}
70
+ ],
71
+ initial_value: "pnpm"
72
+ )
73
+ end
74
+ end
75
+
76
+ return if Clack.handle_cancel(result)
77
+
78
+ # Autocomplete prompt
79
+ color = Clack.autocomplete(
80
+ message: "Pick a theme color:",
81
+ options: %w[red orange yellow green blue indigo violet pink cyan magenta]
82
+ )
83
+ return if Clack.handle_cancel(color)
84
+
85
+ # Select key prompt (quick keyboard shortcuts)
86
+ action = Clack.select_key(
87
+ message: "What would you like to do first?",
88
+ options: [
89
+ {value: "dev", label: "Start dev server", key: "d"},
90
+ {value: "build", label: "Build for production", key: "b"},
91
+ {value: "test", label: "Run tests", key: "t"}
92
+ ]
93
+ )
94
+ return if Clack.handle_cancel(action)
95
+
96
+ # Path prompt
97
+ config_path = Clack.path(
98
+ message: "Select config directory:",
99
+ only_directories: true
100
+ )
101
+ return if Clack.handle_cancel(config_path)
102
+
103
+ # Group multiselect
104
+ stack = Clack.group_multiselect(
105
+ message: "Select additional integrations:",
106
+ options: [
107
+ {
108
+ label: "Frontend",
109
+ options: [
110
+ {value: "react", label: "React"},
111
+ {value: "vue", label: "Vue"},
112
+ {value: "svelte", label: "Svelte"}
113
+ ]
114
+ },
115
+ {
116
+ label: "Backend",
117
+ options: [
118
+ {value: "express", label: "Express"},
119
+ {value: "fastify", label: "Fastify"},
120
+ {value: "hono", label: "Hono"}
121
+ ]
122
+ },
123
+ {
124
+ label: "Database",
125
+ options: [
126
+ {value: "postgres", label: "PostgreSQL"},
127
+ {value: "mysql", label: "MySQL"},
128
+ {value: "sqlite", label: "SQLite"}
129
+ ]
130
+ }
131
+ ],
132
+ required: false
133
+ )
134
+ return if Clack.handle_cancel(stack)
135
+
136
+ # Progress bar
137
+ prog = Clack.progress(total: 100, message: "Downloading assets...")
138
+ prog.start
139
+ 20.times do
140
+ sleep 0.03
141
+ prog.advance(5)
142
+ end
143
+ prog.stop("Assets downloaded!")
144
+
145
+ # Tasks
146
+ Clack.tasks(tasks: [
147
+ {title: "Validating configuration", task: -> { sleep 0.3 }},
148
+ {title: "Generating types", task: -> { sleep 0.4 }},
149
+ {title: "Compiling assets", task: -> { sleep 0.3 }}
150
+ ])
151
+
152
+ # Spinner
153
+ s = Clack.spinner
154
+ s.start "Installing dependencies via #{result[:package_manager]}..."
155
+ sleep 1.0
156
+ s.message "Configuring #{result[:template]} template..."
157
+ sleep 0.6
158
+ s.stop "Project created successfully!"
159
+
160
+ # Summary
161
+ Clack.log.step "Project: #{result[:name]}"
162
+ Clack.log.step "Directory: #{result[:directory]}"
163
+ Clack.log.step "Template: #{result[:template]}"
164
+ Clack.log.step "TypeScript: #{result[:typescript] ? "Yes" : "No"}"
165
+ Clack.log.step "Features: #{result[:features].join(", ")}" unless result[:features].empty?
166
+ Clack.log.step "Color: #{color}"
167
+ Clack.log.step "Action: #{action}"
168
+ Clack.log.step "Config: #{config_path}"
169
+ Clack.log.step "Stack: #{stack.join(", ")}" unless stack.empty?
170
+
171
+ Clack.note <<~MSG, title: "Next steps"
172
+ cd #{result[:directory]}
173
+ #{result[:package_manager]} run dev
174
+ MSG
175
+
176
+ Clack.outro "Happy coding!"
177
+ end
178
+
179
+ run_demo if __FILE__ == $PROGRAM_NAME
@@ -0,0 +1,84 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ # Full demo showcasing Clack Ruby features
5
+ # Run with: ruby examples/full_demo.rb
6
+
7
+ require_relative "../lib/clack" # Or: require "clack" if installed as a gem
8
+
9
+ Clack.intro "create-service"
10
+
11
+ # Text input
12
+ name = Clack.text(
13
+ message: "Service name:",
14
+ placeholder: "order-service"
15
+ )
16
+ exit 1 if Clack.handle_cancel(name)
17
+
18
+ # Password input (masked)
19
+ token = Clack.password(
20
+ message: "GitHub token:",
21
+ mask: "•"
22
+ )
23
+ exit 1 if Clack.handle_cancel(token)
24
+
25
+ # Confirm
26
+ use_openapi = Clack.confirm(
27
+ message: "Generate OpenAPI spec?",
28
+ initial_value: true
29
+ )
30
+ exit 1 if Clack.handle_cancel(use_openapi)
31
+
32
+ # Select (single choice)
33
+ language = Clack.select(
34
+ message: "Language:",
35
+ options: [
36
+ {value: "java", label: "Java 21", hint: "Spring Boot 3"},
37
+ {value: "python", label: "Python 3.12", hint: "FastAPI"},
38
+ {value: "go", label: "Go 1.22", hint: "chi"},
39
+ {value: "node", label: "Node.js 22", hint: "Fastify"}
40
+ ]
41
+ )
42
+ exit 1 if Clack.handle_cancel(language)
43
+
44
+ # Multiselect
45
+ integrations = Clack.multiselect(
46
+ message: "Integrations:",
47
+ options: [
48
+ {value: "postgres", label: "PostgreSQL"},
49
+ {value: "redis", label: "Redis"},
50
+ {value: "kafka", label: "Kafka"},
51
+ {value: "s3", label: "S3"}
52
+ ],
53
+ required: false
54
+ )
55
+ exit 1 if Clack.handle_cancel(integrations)
56
+
57
+ # Progress bar
58
+ prog = Clack.progress(total: 100, message: "Scaffolding...")
59
+ prog.start
60
+ 5.times do
61
+ sleep 0.4
62
+ prog.advance(20)
63
+ end
64
+ prog.stop("Done")
65
+
66
+ # Spinner
67
+ s = Clack.spinner
68
+ s.start "Installing dependencies..."
69
+ sleep 0.8
70
+ s.message "Configuring CI..."
71
+ sleep 0.6
72
+ s.stop "Ready"
73
+
74
+ # Summary
75
+ Clack.log.step "Service: #{name}"
76
+ Clack.log.step "Stack: #{language}"
77
+ Clack.log.step "Integrations: #{integrations.join(", ")}" unless integrations.empty?
78
+
79
+ Clack.note <<~MSG, title: "Next steps"
80
+ cd #{name}
81
+ make dev
82
+ MSG
83
+
84
+ Clack.outro "Ship it"