prompts 0.2.0 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +10 -0
- data/README.md +26 -0
- data/lib/prompts/box.rb +22 -22
- data/lib/prompts/confirm_prompt.rb +7 -7
- data/lib/prompts/content.rb +19 -19
- data/lib/prompts/form.rb +27 -22
- data/lib/prompts/paragraph.rb +1 -1
- data/lib/prompts/pause_prompt.rb +1 -1
- data/lib/prompts/prompt.rb +41 -39
- data/lib/prompts/select_prompt.rb +10 -10
- data/lib/prompts/text_prompt.rb +1 -1
- data/lib/prompts/text_utils.rb +2 -2
- data/lib/prompts/version.rb +1 -1
- data/lib/prompts.rb +3 -5
- metadata +5 -5
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 85f88312acdfb5be0eb5b24924d140c96d01453f742edbc1a1576a8759c37997
|
4
|
+
data.tar.gz: d82a75efffa864d9854a78c29a1f1ae110bbce7b61cd24770067ad1103c29c45
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: ce5e838beb75e5d1e70fcfebcdc5eaf42e5dc77046465af025c2a8c9d78db70092f78a59737da631c12f89b9c292183cc80642c45f6b8b16036d54d5ad2bb83a
|
7
|
+
data.tar.gz: 4223935ce6374fa970f1e5634666a413fad19980105bce0258952a8caab283046a3a3473203ce0439ff168a408d355cea025cca10abe1adaeb80cc4816b7115b
|
data/CHANGELOG.md
CHANGED
@@ -1,5 +1,15 @@
|
|
1
1
|
## [Unreleased]
|
2
2
|
|
3
|
+
## [0.3.0] - 2024-10-08
|
4
|
+
|
5
|
+
- Update `fmt` gem and use new syntax ([@KonnorRogers](https://github.com/fractaledmind/prompts/pull/9))
|
6
|
+
|
7
|
+
## [0.2.1] - 2024-08-22
|
8
|
+
|
9
|
+
- Add `Form.submit` method
|
10
|
+
- All passing keyword arguments to `Form` prompt methods
|
11
|
+
- Return the key of the options hash when using `SelectPrompt`
|
12
|
+
|
3
13
|
## [0.2.0] - 2024-08-20
|
4
14
|
|
5
15
|
- Add `TextPrompt`
|
data/README.md
CHANGED
@@ -256,6 +256,32 @@ which generates a terminal screen like this (this representation doesn't show co
|
|
256
256
|
<b>Press Enter ⏎ to continue...</b> |
|
257
257
|
</pre>
|
258
258
|
|
259
|
+
## Forms
|
260
|
+
|
261
|
+
Often, you will have multiple prompts that will be displayed in sequence to collect information before performing additional actions. You may use the `Prompts::Form` class to create a grouped set of prompts for the user to complete:
|
262
|
+
|
263
|
+
```ruby
|
264
|
+
responses = Prompts::Form.submit do |form|
|
265
|
+
form.text(
|
266
|
+
label: "What is your name?",
|
267
|
+
required: true
|
268
|
+
)
|
269
|
+
form.select(
|
270
|
+
label: "What role should the user have?",
|
271
|
+
options: {
|
272
|
+
member: "Member",
|
273
|
+
contributor: "Contributor",
|
274
|
+
owner: "Owner",
|
275
|
+
}
|
276
|
+
)
|
277
|
+
form.confirm(
|
278
|
+
label: 'Do you accept the terms?'
|
279
|
+
)
|
280
|
+
end
|
281
|
+
```
|
282
|
+
|
283
|
+
The `submit` method will return an array containing all of the responses from the form's prompts.
|
284
|
+
|
259
285
|
## Development
|
260
286
|
|
261
287
|
After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake test` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
|
data/lib/prompts/box.rb
CHANGED
@@ -4,10 +4,10 @@ module Prompts
|
|
4
4
|
class Box
|
5
5
|
include TextUtils
|
6
6
|
|
7
|
-
SOLID_BORDER
|
8
|
-
DOUBLE_BORDER
|
9
|
-
HEAVY_BORDER
|
10
|
-
ROUNDED_BORDER
|
7
|
+
SOLID_BORDER = {top_left: "┌", top_right: "┐", bottom_left: "└", bottom_right: "┘", horizontal: "─", vertical: "│"}.freeze
|
8
|
+
DOUBLE_BORDER = {top_left: "╔", top_right: "╗", bottom_left: "╚", bottom_right: "╝", horizontal: "═", vertical: "║"}.freeze
|
9
|
+
HEAVY_BORDER = {top_left: "┏", top_right: "┓", bottom_left: "┗", bottom_right: "┛", horizontal: "━", vertical: "┃"}.freeze
|
10
|
+
ROUNDED_BORDER = {top_left: "╭", top_right: "╮", bottom_left: "╰", bottom_right: "╯", horizontal: "─", vertical: "│"}.freeze
|
11
11
|
|
12
12
|
def initialize(width: MAX_WIDTH, padded: false, border_color: nil, border_style: :rounded)
|
13
13
|
@width = width
|
@@ -15,11 +15,11 @@ module Prompts
|
|
15
15
|
@border_color = border_color
|
16
16
|
@line_padding = SPACE * 1
|
17
17
|
@border_parts = case border_style
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
18
|
+
when :solid then SOLID_BORDER
|
19
|
+
when :double then DOUBLE_BORDER
|
20
|
+
when :heavy then HEAVY_BORDER
|
21
|
+
else ROUNDED_BORDER
|
22
|
+
end
|
23
23
|
@content = []
|
24
24
|
end
|
25
25
|
|
@@ -53,19 +53,19 @@ module Prompts
|
|
53
53
|
|
54
54
|
private
|
55
55
|
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
56
|
+
def top_border
|
57
|
+
border = @border_parts[:top_left] + @border_parts[:horizontal] * (@width - 2) + @border_parts[:top_right]
|
58
|
+
Fmt("#{@line_padding}%{border}|>#{@border_color}", border: border)
|
59
|
+
end
|
60
60
|
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
61
|
+
def bottom_border
|
62
|
+
border = @border_parts[:bottom_left] + @border_parts[:horizontal] * (@width - 2) + @border_parts[:bottom_right]
|
63
|
+
Fmt("#{@line_padding}%{border}|>#{@border_color}", border: border)
|
64
|
+
end
|
65
65
|
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
66
|
+
def align(text, alignment, between: @border_parts[:vertical])
|
67
|
+
formatted_boundary = Fmt("%{boundary}|>#{@border_color}", boundary: between)
|
68
|
+
wrap_text(text, width: @width, line_prefix: formatted_boundary + SPACE, line_suffix: SPACE + formatted_boundary, alignment: alignment)
|
69
|
+
end
|
70
70
|
end
|
71
|
-
end
|
71
|
+
end
|
@@ -3,7 +3,7 @@
|
|
3
3
|
module Prompts
|
4
4
|
class ConfirmPrompt < Prompt
|
5
5
|
def initialize(...)
|
6
|
-
super
|
6
|
+
super
|
7
7
|
|
8
8
|
@prompt = if @default == false
|
9
9
|
"Choose [y/N]:"
|
@@ -20,12 +20,12 @@ module Prompts
|
|
20
20
|
|
21
21
|
private
|
22
22
|
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
end
|
23
|
+
def resolve_choice_from(response)
|
24
|
+
case response
|
25
|
+
when "y", "Y" then true
|
26
|
+
when "n", "N" then false
|
27
|
+
when "" then @default_boolean
|
29
28
|
end
|
29
|
+
end
|
30
30
|
end
|
31
31
|
end
|
data/lib/prompts/content.rb
CHANGED
@@ -43,28 +43,28 @@ module Prompts
|
|
43
43
|
|
44
44
|
private
|
45
45
|
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
46
|
+
def clear_screen
|
47
|
+
jump_cursor_to_top
|
48
|
+
erase_down
|
49
|
+
end
|
50
50
|
|
51
|
-
|
52
|
-
|
53
|
-
|
51
|
+
def render_frame
|
52
|
+
@frame_stack << @slots.dup
|
53
|
+
OUTPUT.puts SPACE
|
54
54
|
|
55
|
-
|
55
|
+
return if @slots.empty?
|
56
56
|
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
57
|
+
OUTPUT.puts @slots.join("\n")
|
58
|
+
OUTPUT.puts SPACE
|
59
|
+
@slots.clear
|
60
|
+
end
|
61
61
|
|
62
|
-
|
63
|
-
|
64
|
-
|
62
|
+
def jump_cursor_to_top
|
63
|
+
OUTPUT.print "\033[H"
|
64
|
+
end
|
65
65
|
|
66
|
-
|
67
|
-
|
68
|
-
|
66
|
+
def erase_down
|
67
|
+
OUTPUT.print "\e[J"
|
68
|
+
end
|
69
69
|
end
|
70
|
-
end
|
70
|
+
end
|
data/lib/prompts/form.rb
CHANGED
@@ -2,47 +2,52 @@
|
|
2
2
|
|
3
3
|
module Prompts
|
4
4
|
class Form
|
5
|
-
def
|
6
|
-
|
5
|
+
def self.submit(&block)
|
6
|
+
instance = new
|
7
|
+
yield instance if block
|
8
|
+
instance.submit
|
9
|
+
end
|
10
|
+
|
11
|
+
def initialize
|
12
|
+
@content = Prompts::Content.new
|
7
13
|
@prompts = []
|
8
14
|
@results = []
|
9
15
|
end
|
10
16
|
|
11
17
|
def content(&block)
|
12
|
-
@content = Prompts::Content.new
|
13
18
|
yield @content
|
14
19
|
@content
|
15
20
|
end
|
16
21
|
|
17
|
-
def text(&block)
|
18
|
-
prompt = TextPrompt.new
|
19
|
-
yield(prompt)
|
22
|
+
def text(label: nil, prompt: "> ", hint: nil, default: nil, required: false, validate: nil, &block)
|
23
|
+
prompt = TextPrompt.new(label: label, prompt: prompt, hint: hint, default: default, required: required, validate: validate)
|
24
|
+
yield(prompt) if block
|
20
25
|
prepend_form_content_to_prompt(prompt)
|
21
26
|
@prompts << prompt
|
22
27
|
end
|
23
28
|
|
24
|
-
def select(&block)
|
25
|
-
prompt = SelectPrompt.new
|
26
|
-
yield(prompt)
|
29
|
+
def select(label: nil, options: nil, prompt: "> ", hint: nil, default: nil, validate: nil, &block)
|
30
|
+
prompt = SelectPrompt.new(label: label, options: options, prompt: prompt, hint: hint, default: default, validate: validate)
|
31
|
+
yield(prompt) if block
|
27
32
|
prepend_form_content_to_prompt(prompt)
|
28
33
|
@prompts << prompt
|
29
34
|
end
|
30
35
|
|
31
|
-
def pause(&block)
|
32
|
-
prompt = PausePrompt.new
|
33
|
-
yield(prompt)
|
36
|
+
def pause(label: nil, prompt: "> ", hint: nil, default: nil, required: false, validate: nil, &block)
|
37
|
+
prompt = PausePrompt.new(label: label, prompt: prompt, hint: hint, default: default, required: required, validate: validate)
|
38
|
+
yield(prompt) if block
|
34
39
|
prepend_form_content_to_prompt(prompt)
|
35
40
|
@prompts << prompt
|
36
41
|
end
|
37
42
|
|
38
|
-
def confirm(&block)
|
39
|
-
prompt = ConfirmPrompt.new
|
40
|
-
yield(prompt)
|
43
|
+
def confirm(label: nil, prompt: "> ", hint: nil, default: nil, required: false, validate: nil, &block)
|
44
|
+
prompt = ConfirmPrompt.new(label: label, prompt: prompt, hint: hint, default: default, required: required, validate: validate)
|
45
|
+
yield(prompt) if block
|
41
46
|
prepend_form_content_to_prompt(prompt)
|
42
47
|
@prompts << prompt
|
43
48
|
end
|
44
49
|
|
45
|
-
def
|
50
|
+
def submit
|
46
51
|
@prompts.each do |prompt|
|
47
52
|
@results << prompt.ask
|
48
53
|
end
|
@@ -51,10 +56,10 @@ module Prompts
|
|
51
56
|
|
52
57
|
private
|
53
58
|
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
+
def prepend_form_content_to_prompt(prompt)
|
60
|
+
prompt.prepare_content
|
61
|
+
@content.gap
|
62
|
+
prompt.prepend_content(*@content.slots)
|
63
|
+
end
|
59
64
|
end
|
60
|
-
end
|
65
|
+
end
|
data/lib/prompts/paragraph.rb
CHANGED
data/lib/prompts/pause_prompt.rb
CHANGED
data/lib/prompts/prompt.rb
CHANGED
@@ -33,6 +33,7 @@ module Prompts
|
|
33
33
|
@content
|
34
34
|
end
|
35
35
|
|
36
|
+
# standard:disable Style/TrivialAccessors
|
36
37
|
def label(label)
|
37
38
|
@label = label
|
38
39
|
end
|
@@ -44,6 +45,7 @@ module Prompts
|
|
44
45
|
def default(default)
|
45
46
|
@default = default
|
46
47
|
end
|
48
|
+
# standard:enable Style/TrivialAccessors
|
47
49
|
|
48
50
|
def ask
|
49
51
|
prepare_content if !@content_prepared
|
@@ -54,12 +56,12 @@ module Prompts
|
|
54
56
|
@content.render
|
55
57
|
*initial_prompt_lines, last_prompt_line = formatted_prompt
|
56
58
|
puts initial_prompt_lines.join("\n") if initial_prompt_lines.any?
|
57
|
-
response = Reline.readline(last_prompt_line,
|
59
|
+
response = Reline.readline(last_prompt_line, _history = false).chomp
|
58
60
|
@choice = resolve_choice_from(response)
|
59
61
|
|
60
62
|
if (@error = ensure_validity(response))
|
61
63
|
@content.reset!
|
62
|
-
@content.paragraph Fmt("%{error}red
|
64
|
+
@content.paragraph Fmt("%{error}|>red|>bold", error: @error + " Try again (×#{@attempts})...")
|
63
65
|
@attempts += 1
|
64
66
|
next
|
65
67
|
else
|
@@ -87,53 +89,53 @@ module Prompts
|
|
87
89
|
|
88
90
|
private
|
89
91
|
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
end
|
92
|
+
def prepare_default
|
93
|
+
Reline.pre_input_hook = -> do
|
94
|
+
Reline.insert_text @default.to_s
|
95
|
+
# Remove the hook right away.
|
96
|
+
Reline.pre_input_hook = nil
|
96
97
|
end
|
98
|
+
end
|
97
99
|
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
end
|
103
|
-
|
104
|
-
if @validate
|
105
|
-
@validations << @validate
|
106
|
-
end
|
100
|
+
def prepare_validations
|
101
|
+
if @required
|
102
|
+
error_message = @required.is_a?(String) ? @required : "Value cannot be empty."
|
103
|
+
@validations << ->(input) { error_message if input.empty? }
|
107
104
|
end
|
108
105
|
|
109
|
-
|
110
|
-
|
106
|
+
if @validate
|
107
|
+
@validations << @validate
|
111
108
|
end
|
109
|
+
end
|
112
110
|
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
@formatted_prompt ||= Paragraph.new(ansi_prompt, width: MAX_WIDTH).lines
|
117
|
-
end
|
111
|
+
def resolve_choice_from(response)
|
112
|
+
response
|
113
|
+
end
|
118
114
|
|
119
|
-
|
120
|
-
|
121
|
-
|
115
|
+
def formatted_prompt
|
116
|
+
prompt_with_space = @prompt.end_with?(SPACE) ? @prompt : @prompt + SPACE
|
117
|
+
ansi_prompt = Fmt("%{prompt}|>faint|>bold", prompt: prompt_with_space)
|
118
|
+
@formatted_prompt ||= Paragraph.new(ansi_prompt, width: MAX_WIDTH).lines
|
119
|
+
end
|
122
120
|
|
123
|
-
|
124
|
-
|
125
|
-
|
121
|
+
def formatted_label
|
122
|
+
Fmt("%{label}|>cyan|>bold %{instructions}|>faint|>italic", label: @label, instructions: @instructions ? "(#{@instructions})" : "")
|
123
|
+
end
|
126
124
|
|
127
|
-
|
128
|
-
|
129
|
-
|
125
|
+
def formatted_hint
|
126
|
+
Fmt("%{hint}|>faint|>bold", hint: @hint)
|
127
|
+
end
|
130
128
|
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
129
|
+
def formatted_error
|
130
|
+
Fmt("%{error}|>red|>bold", error: @error + " Try again (×#{@attempts})...")
|
131
|
+
end
|
132
|
+
|
133
|
+
def ensure_validity(response)
|
134
|
+
@validations.each do |validation|
|
135
|
+
result = validation.call(response)
|
136
|
+
return result if result
|
137
137
|
end
|
138
|
+
nil
|
139
|
+
end
|
138
140
|
end
|
139
141
|
end
|
@@ -12,34 +12,34 @@ module Prompts
|
|
12
12
|
super(**kwargs)
|
13
13
|
|
14
14
|
@options = options.is_a?(Array) ? options.to_h { |item| [item, item] } : options
|
15
|
-
if (index = @options.keys.index(@default))
|
16
|
-
|
17
|
-
else
|
18
|
-
@default = nil
|
15
|
+
@default = if (index = @options.keys.index(@default))
|
16
|
+
index + 1
|
19
17
|
end
|
20
18
|
@instructions = "Enter the number of your choice"
|
21
19
|
@hint ||= "Type your response and press Enter ⏎"
|
22
20
|
@validations << ->(choice) { "Invalid choice." if !choice.to_i.between?(1, @options.size) }
|
23
21
|
end
|
24
22
|
|
23
|
+
# standard:disable Style/TrivialAccessors
|
25
24
|
def options(options)
|
26
25
|
@options = options
|
27
26
|
end
|
27
|
+
# standard:enable Style/TrivialAccessors
|
28
28
|
|
29
29
|
def prepare_content
|
30
30
|
super
|
31
31
|
@options.each_with_index do |(key, value), index|
|
32
|
-
@content.paragraph Fmt("%{prefix}faint
|
32
|
+
@content.paragraph Fmt("%{prefix}|>faint|>bold %{option}", prefix: "#{index + 1}.", option: value)
|
33
33
|
end
|
34
34
|
@content
|
35
35
|
end
|
36
36
|
|
37
37
|
private
|
38
38
|
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
39
|
+
def resolve_choice_from(response)
|
40
|
+
choice = response.to_i
|
41
|
+
key, _value = @options.to_a[choice - 1]
|
42
|
+
key
|
43
|
+
end
|
44
44
|
end
|
45
45
|
end
|
data/lib/prompts/text_prompt.rb
CHANGED
data/lib/prompts/text_utils.rb
CHANGED
@@ -5,7 +5,7 @@ require "unicode/emoji"
|
|
5
5
|
|
6
6
|
module Prompts
|
7
7
|
module TextUtils
|
8
|
-
ANSI_REGEX = /\e\[[0-9;]*[a-zA-Z]
|
8
|
+
ANSI_REGEX = /\e\[[0-9;]*[a-zA-Z]/
|
9
9
|
|
10
10
|
def wrap_text(text, width:, line_prefix: EMPTY, line_suffix: EMPTY, alignment: :left)
|
11
11
|
words = text.scan(Regexp.union(/\S+/, ANSI_REGEX))
|
@@ -55,4 +55,4 @@ module Prompts
|
|
55
55
|
text.gsub(ANSI_REGEX, EMPTY)
|
56
56
|
end
|
57
57
|
end
|
58
|
-
end
|
58
|
+
end
|
data/lib/prompts/version.rb
CHANGED
data/lib/prompts.rb
CHANGED
@@ -2,10 +2,8 @@
|
|
2
2
|
|
3
3
|
require "io/console"
|
4
4
|
require "reline"
|
5
|
+
require "rainbow" # this needs to come before require "fmt"
|
5
6
|
require "fmt"
|
6
|
-
require "rainbow"
|
7
|
-
|
8
|
-
Fmt.add_rainbow_filters
|
9
7
|
|
10
8
|
require_relative "prompts/version"
|
11
9
|
require_relative "prompts/prompt"
|
@@ -20,8 +18,8 @@ require_relative "prompts/select_prompt"
|
|
20
18
|
require_relative "prompts/form"
|
21
19
|
|
22
20
|
module Prompts
|
23
|
-
EMPTY = ""
|
24
|
-
SPACE = " "
|
21
|
+
EMPTY = ""
|
22
|
+
SPACE = " "
|
25
23
|
MAX_WIDTH = 80
|
26
24
|
OUTPUT = $stdout
|
27
25
|
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: prompts
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.3.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Stephen Margheim
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2024-08
|
11
|
+
date: 2024-10-08 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: unicode-display_width
|
@@ -58,14 +58,14 @@ dependencies:
|
|
58
58
|
requirements:
|
59
59
|
- - ">="
|
60
60
|
- !ruby/object:Gem::Version
|
61
|
-
version:
|
61
|
+
version: 0.3.0
|
62
62
|
type: :runtime
|
63
63
|
prerelease: false
|
64
64
|
version_requirements: !ruby/object:Gem::Requirement
|
65
65
|
requirements:
|
66
66
|
- - ">="
|
67
67
|
- !ruby/object:Gem::Version
|
68
|
-
version:
|
68
|
+
version: 0.3.0
|
69
69
|
- !ruby/object:Gem::Dependency
|
70
70
|
name: rainbow
|
71
71
|
requirement: !ruby/object:Gem::Requirement
|
@@ -128,7 +128,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
128
128
|
- !ruby/object:Gem::Version
|
129
129
|
version: '0'
|
130
130
|
requirements: []
|
131
|
-
rubygems_version: 3.5.
|
131
|
+
rubygems_version: 3.5.11
|
132
132
|
signing_key:
|
133
133
|
specification_version: 4
|
134
134
|
summary: Beautiful and user-friendly forms for your command-line Ruby applications.
|