asktty 1.0.1 → 2.0.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/README.md +9 -6
- data/lib/asktty/internal/options.rb +13 -14
- data/lib/asktty/internal/rendering.rb +18 -23
- data/lib/asktty/internal/terminal.rb +9 -1
- data/lib/asktty/internal/text_input_prompt.rb +25 -17
- data/lib/asktty/internal/validation.rb +4 -4
- data/lib/asktty/prompts/confirm_prompt.rb +23 -8
- data/lib/asktty/prompts/input_prompt.rb +9 -10
- data/lib/asktty/prompts/multi_select_prompt.rb +25 -24
- data/lib/asktty/prompts/select_prompt.rb +29 -19
- data/lib/asktty/prompts/text_prompt.rb +9 -10
- data/lib/asktty/version.rb +1 -1
- data/lib/asktty.rb +5 -4
- metadata +2 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 7afce23120ce01740dd6c9f0e405080565efd3c22546361508611e8573a051aa
|
|
4
|
+
data.tar.gz: 8c2e75af399a24f63d9230112acbb07152d604f00da0d0cdc95098b9cf48fb31
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 2c164a8cd20c460cf1baa0739244155c2f24f862f6b987617245eeac91e1e55938b88d8c109d7ce87df1c05f1181c04e4e983c5ac2febdc8442ecb79364c5d84
|
|
7
|
+
data.tar.gz: 17f6e9e671a4ac84b1cb358f7648c03561b9212f06f4cafa7190f002e4c67f552b03f866236104da19cc772f365b3e147b63fdf03b3acc364a830d4136f275f1
|
data/README.md
CHANGED
|
@@ -6,13 +6,13 @@ A Ruby gem for interactive terminal prompts.
|
|
|
6
6
|
|
|
7
7
|
## Installation
|
|
8
8
|
|
|
9
|
-
Install it
|
|
9
|
+
Install it globally:
|
|
10
10
|
|
|
11
11
|
```sh
|
|
12
12
|
gem install asktty
|
|
13
13
|
```
|
|
14
14
|
|
|
15
|
-
or, add it to your application
|
|
15
|
+
or, add it to your application Gemfile:
|
|
16
16
|
|
|
17
17
|
```sh
|
|
18
18
|
bundle add asktty
|
|
@@ -37,7 +37,7 @@ name = AskTTY::InputPrompt.ask(
|
|
|
37
37
|
### Text Prompt
|
|
38
38
|
|
|
39
39
|
```ruby
|
|
40
|
-
|
|
40
|
+
project = AskTTY::TextPrompt.ask(
|
|
41
41
|
title: "Ruby Project",
|
|
42
42
|
placeholder: "AskTTY\nTerminal prompts for Ruby"
|
|
43
43
|
)
|
|
@@ -46,7 +46,7 @@ notes = AskTTY::TextPrompt.ask(
|
|
|
46
46
|
### Select Prompt
|
|
47
47
|
|
|
48
48
|
```ruby
|
|
49
|
-
|
|
49
|
+
experience = AskTTY::SelectPrompt.ask(
|
|
50
50
|
title: "Experience Level",
|
|
51
51
|
options: [
|
|
52
52
|
{ label: "Beginner", value: :beginner },
|
|
@@ -57,10 +57,13 @@ drink = AskTTY::SelectPrompt.ask(
|
|
|
57
57
|
)
|
|
58
58
|
```
|
|
59
59
|
|
|
60
|
+
Select and multi-select options must be provided as a non-empty array of symbol-keyed `{ label:, value: }` hashes.
|
|
61
|
+
Option values must be unique.
|
|
62
|
+
|
|
60
63
|
### MultiSelect Prompt
|
|
61
64
|
|
|
62
65
|
```ruby
|
|
63
|
-
|
|
66
|
+
topics = AskTTY::MultiSelectPrompt.ask(
|
|
64
67
|
title: "Topics",
|
|
65
68
|
details: "Select all topics you are interested in.",
|
|
66
69
|
options: [
|
|
@@ -86,7 +89,7 @@ confirmed = AskTTY::ConfirmPrompt.ask(
|
|
|
86
89
|
|
|
87
90
|
### Validation
|
|
88
91
|
|
|
89
|
-
Pass a block that returns `true` when the current value is valid, or
|
|
92
|
+
Pass a block that returns `true` when the current value is valid, or a non-empty error message when it is not:
|
|
90
93
|
|
|
91
94
|
```ruby
|
|
92
95
|
name = AskTTY::InputPrompt.ask(title: "Name") do |value|
|
|
@@ -3,32 +3,31 @@
|
|
|
3
3
|
module AskTTY
|
|
4
4
|
module Internal
|
|
5
5
|
module Options
|
|
6
|
-
Option =
|
|
6
|
+
Option = Data.define(:label, :value)
|
|
7
|
+
|
|
8
|
+
private_constant :Option
|
|
7
9
|
|
|
8
10
|
module_function
|
|
9
11
|
|
|
10
12
|
def normalize(options)
|
|
11
|
-
raise AskTTY::Error, "options must
|
|
13
|
+
raise AskTTY::Error, "options must be a non-empty array" unless options.is_a?(Array) && !options.empty?
|
|
12
14
|
|
|
13
|
-
|
|
14
|
-
next option if option.is_a?(Option)
|
|
15
|
+
values = []
|
|
15
16
|
|
|
17
|
+
options.map do |option|
|
|
16
18
|
raise AskTTY::Error, "options must be hashes with label and value" unless option.is_a?(Hash)
|
|
17
19
|
|
|
18
|
-
label
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
value = option["value"] if option.key?("value")
|
|
20
|
+
unless option.key?(:label) && option.key?(:value) && !option[:label].nil?
|
|
21
|
+
raise AskTTY::Error, "options must include label and value"
|
|
22
|
+
end
|
|
22
23
|
|
|
23
|
-
|
|
24
|
+
value = option[:value]
|
|
25
|
+
raise AskTTY::Error, "option values must be unique" if values.include?(value)
|
|
24
26
|
|
|
25
|
-
|
|
27
|
+
values << value
|
|
28
|
+
Option.new(option[:label].to_s, value)
|
|
26
29
|
end
|
|
27
30
|
end
|
|
28
|
-
|
|
29
|
-
def values(options)
|
|
30
|
-
options.map(&:value)
|
|
31
|
-
end
|
|
32
31
|
end
|
|
33
32
|
end
|
|
34
33
|
end
|
|
@@ -42,7 +42,7 @@ module AskTTY
|
|
|
42
42
|
summary = value.to_s.empty? ? prefix : "#{prefix} #{value}"
|
|
43
43
|
lines = wrap(summary, content_width(width))
|
|
44
44
|
|
|
45
|
-
frame(
|
|
45
|
+
frame(lines.map { |line| ANSIStyle.text(line) })
|
|
46
46
|
end
|
|
47
47
|
|
|
48
48
|
def wrap(text, width)
|
|
@@ -53,6 +53,16 @@ module AskTTY
|
|
|
53
53
|
split_lines(text.to_s).flat_map { |line| wrap_exact_line(line, width) }
|
|
54
54
|
end
|
|
55
55
|
|
|
56
|
+
def prefixed_lines(text, prefix:, width:, style:)
|
|
57
|
+
prefix_width = display_width(prefix)
|
|
58
|
+
wrapped = wrap(text, [width - prefix_width, 1].max)
|
|
59
|
+
|
|
60
|
+
wrapped.each_with_index.map do |line, index|
|
|
61
|
+
current_prefix = index.zero? ? prefix : (" " * prefix_width)
|
|
62
|
+
current_prefix + style.call(line)
|
|
63
|
+
end
|
|
64
|
+
end
|
|
65
|
+
|
|
56
66
|
def header_lines(title, details, width:)
|
|
57
67
|
lines = wrap(title.to_s, width).map { |line| ANSIStyle.title(line) }
|
|
58
68
|
|
|
@@ -89,7 +99,8 @@ module AskTTY
|
|
|
89
99
|
segment_width = display_width(segment)
|
|
90
100
|
|
|
91
101
|
if width.to_i.positive? && total_width + segment_width > width
|
|
92
|
-
line
|
|
102
|
+
ellipsis = line.empty? ? "…" : " …"
|
|
103
|
+
line << ellipsis if total_width + display_width(ellipsis) <= width
|
|
93
104
|
break
|
|
94
105
|
end
|
|
95
106
|
|
|
@@ -129,27 +140,6 @@ module AskTTY
|
|
|
129
140
|
text.split("\n", -1)
|
|
130
141
|
end
|
|
131
142
|
|
|
132
|
-
def style_submitted_lines(lines, title_length:)
|
|
133
|
-
remaining_title_length = title_length
|
|
134
|
-
|
|
135
|
-
lines.map do |line|
|
|
136
|
-
line_graphemes = graphemes(line)
|
|
137
|
-
|
|
138
|
-
if remaining_title_length >= line_graphemes.length
|
|
139
|
-
remaining_title_length -= line_graphemes.length
|
|
140
|
-
ANSIStyle.title(line)
|
|
141
|
-
elsif remaining_title_length.positive?
|
|
142
|
-
title_text = line_graphemes[0, remaining_title_length].join
|
|
143
|
-
value_text = line_graphemes[remaining_title_length..].to_a.join
|
|
144
|
-
remaining_title_length = 0
|
|
145
|
-
|
|
146
|
-
ANSIStyle.title(title_text) + ANSIStyle.text(value_text)
|
|
147
|
-
else
|
|
148
|
-
ANSIStyle.text(line)
|
|
149
|
-
end
|
|
150
|
-
end
|
|
151
|
-
end
|
|
152
|
-
|
|
153
143
|
def wrap_line(line, width)
|
|
154
144
|
return [""] if line.empty?
|
|
155
145
|
|
|
@@ -157,6 +147,11 @@ module AskTTY
|
|
|
157
147
|
remaining = graphemes(line)
|
|
158
148
|
|
|
159
149
|
until remaining.empty?
|
|
150
|
+
if lines.any?
|
|
151
|
+
remaining = remaining.drop_while { |grapheme| grapheme.match?(/\s/) }
|
|
152
|
+
break if remaining.empty?
|
|
153
|
+
end
|
|
154
|
+
|
|
160
155
|
segment, last_whitespace_index = take_segment(remaining, width)
|
|
161
156
|
|
|
162
157
|
if segment.length == remaining.length
|
|
@@ -5,6 +5,11 @@ require "io/console"
|
|
|
5
5
|
module AskTTY
|
|
6
6
|
module Internal
|
|
7
7
|
class Terminal
|
|
8
|
+
ESCAPE_START_TIMEOUT = 0.1
|
|
9
|
+
ESCAPE_CONTINUATION_TIMEOUT = 0.01
|
|
10
|
+
|
|
11
|
+
private_constant :ESCAPE_START_TIMEOUT, :ESCAPE_CONTINUATION_TIMEOUT
|
|
12
|
+
|
|
8
13
|
attr_reader :width
|
|
9
14
|
|
|
10
15
|
def self.open(&)
|
|
@@ -79,13 +84,16 @@ module AskTTY
|
|
|
79
84
|
|
|
80
85
|
def read_escape_sequence
|
|
81
86
|
sequence = +""
|
|
87
|
+
timeout = ESCAPE_START_TIMEOUT
|
|
82
88
|
|
|
83
|
-
while @input.wait_readable(
|
|
89
|
+
while @input.wait_readable(timeout)
|
|
84
90
|
chunk = @input.read_nonblock(1, exception: false)
|
|
85
91
|
break if chunk == :wait_readable || chunk.nil?
|
|
86
92
|
|
|
87
93
|
sequence << chunk
|
|
88
94
|
break if chunk.match?(/[A-Za-z~]/)
|
|
95
|
+
|
|
96
|
+
timeout = ESCAPE_CONTINUATION_TIMEOUT
|
|
89
97
|
end
|
|
90
98
|
|
|
91
99
|
sequence
|
|
@@ -14,7 +14,8 @@ module AskTTY
|
|
|
14
14
|
|
|
15
15
|
def ask
|
|
16
16
|
value = @value.dup
|
|
17
|
-
|
|
17
|
+
error_message = nil
|
|
18
|
+
validation_checked = false
|
|
18
19
|
|
|
19
20
|
Terminal.open do |session|
|
|
20
21
|
loop do
|
|
@@ -22,8 +23,7 @@ module AskTTY
|
|
|
22
23
|
render(
|
|
23
24
|
value,
|
|
24
25
|
width: session.width,
|
|
25
|
-
|
|
26
|
-
error_message: validation_message(value, validation_active)
|
|
26
|
+
error_message: error_message
|
|
27
27
|
)
|
|
28
28
|
)
|
|
29
29
|
|
|
@@ -31,8 +31,12 @@ module AskTTY
|
|
|
31
31
|
|
|
32
32
|
case key
|
|
33
33
|
when :enter
|
|
34
|
-
|
|
35
|
-
|
|
34
|
+
unless validation_checked
|
|
35
|
+
error_message = validation_message(value)
|
|
36
|
+
validation_checked = true
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
next if error_message
|
|
36
40
|
|
|
37
41
|
session.render(submitted_render(value, width: session.width))
|
|
38
42
|
return value
|
|
@@ -40,16 +44,21 @@ module AskTTY
|
|
|
40
44
|
next unless @multiline
|
|
41
45
|
|
|
42
46
|
value << "\n"
|
|
43
|
-
|
|
47
|
+
error_message = validation_message(value)
|
|
48
|
+
validation_checked = true
|
|
44
49
|
when :backspace
|
|
45
50
|
updated_value = Rendering.chop_grapheme(value)
|
|
46
|
-
|
|
51
|
+
next if updated_value == value
|
|
52
|
+
|
|
47
53
|
value = updated_value
|
|
54
|
+
error_message = validation_message(value)
|
|
55
|
+
validation_checked = true
|
|
48
56
|
when String
|
|
49
57
|
next unless printable?(key)
|
|
50
58
|
|
|
51
59
|
value << key
|
|
52
|
-
|
|
60
|
+
error_message = validation_message(value)
|
|
61
|
+
validation_checked = true
|
|
53
62
|
end
|
|
54
63
|
end
|
|
55
64
|
end
|
|
@@ -57,11 +66,11 @@ module AskTTY
|
|
|
57
66
|
|
|
58
67
|
private
|
|
59
68
|
|
|
60
|
-
def render(value, width:,
|
|
69
|
+
def render(value, width:, error_message: nil)
|
|
61
70
|
Rendering.prompt_frame(
|
|
62
71
|
title: @title, details: @details, help_items: help_items, error_message: error_message, width: width
|
|
63
72
|
) do |content_width|
|
|
64
|
-
body_lines(value, content_width: content_width
|
|
73
|
+
body_lines(value, content_width: content_width)
|
|
65
74
|
end
|
|
66
75
|
end
|
|
67
76
|
|
|
@@ -69,15 +78,15 @@ module AskTTY
|
|
|
69
78
|
Rendering.submitted_frame(@title, summary_value(value), width: width)
|
|
70
79
|
end
|
|
71
80
|
|
|
72
|
-
def body_lines(value, content_width
|
|
73
|
-
return placeholder_lines(content_width) if @placeholder && value.empty?
|
|
81
|
+
def body_lines(value, content_width:)
|
|
82
|
+
return placeholder_lines(content_width) if @placeholder && value.empty?
|
|
74
83
|
|
|
75
|
-
render_segments(value_lines(value), content_width: content_width, show_cursor:
|
|
84
|
+
render_segments(value_lines(value), content_width: content_width, show_cursor: true)
|
|
76
85
|
end
|
|
77
86
|
|
|
78
87
|
def placeholder_lines(content_width)
|
|
79
88
|
render_segments(
|
|
80
|
-
@placeholder.
|
|
89
|
+
@placeholder.split("\n", -1),
|
|
81
90
|
content_width: content_width,
|
|
82
91
|
show_cursor: false,
|
|
83
92
|
first_style: method(:placeholder_first_line),
|
|
@@ -114,7 +123,6 @@ module AskTTY
|
|
|
114
123
|
|
|
115
124
|
segments.each_with_index.flat_map do |segment, segment_index|
|
|
116
125
|
wrapped_segments = Rendering.wrap_exact(segment, wrap_width)
|
|
117
|
-
wrapped_segments = [""] if wrapped_segments.empty?
|
|
118
126
|
|
|
119
127
|
if show_cursor && segment_index == last_index && Rendering.display_width(wrapped_segments.last) >= wrap_width
|
|
120
128
|
wrapped_segments << ""
|
|
@@ -140,8 +148,8 @@ module AskTTY
|
|
|
140
148
|
value.tr("\n", " ")
|
|
141
149
|
end
|
|
142
150
|
|
|
143
|
-
def validation_message(value
|
|
144
|
-
Validation.message_for(value, @validator
|
|
151
|
+
def validation_message(value)
|
|
152
|
+
Validation.message_for(value, @validator)
|
|
145
153
|
end
|
|
146
154
|
end
|
|
147
155
|
end
|
|
@@ -5,14 +5,14 @@ module AskTTY
|
|
|
5
5
|
module Validation
|
|
6
6
|
module_function
|
|
7
7
|
|
|
8
|
-
def message_for(value, validator
|
|
9
|
-
return nil unless
|
|
8
|
+
def message_for(value, validator)
|
|
9
|
+
return nil unless validator
|
|
10
10
|
|
|
11
11
|
result = validator.call(value)
|
|
12
12
|
return nil if result == true
|
|
13
|
-
return result if result.is_a?(String)
|
|
13
|
+
return result if result.is_a?(String) && !result.empty?
|
|
14
14
|
|
|
15
|
-
raise AskTTY::Error, "validator must return true or
|
|
15
|
+
raise AskTTY::Error, "validator must return true or a non-empty error message"
|
|
16
16
|
end
|
|
17
17
|
end
|
|
18
18
|
end
|
|
@@ -6,6 +6,8 @@ module AskTTY
|
|
|
6
6
|
new(title: title, details: details, value: value, validator: validator).ask
|
|
7
7
|
end
|
|
8
8
|
|
|
9
|
+
private_class_method :new
|
|
10
|
+
|
|
9
11
|
def initialize(title:, details: nil, value: false, validator: nil)
|
|
10
12
|
@title = title.to_s
|
|
11
13
|
@details = details&.to_s
|
|
@@ -17,27 +19,40 @@ module AskTTY
|
|
|
17
19
|
end
|
|
18
20
|
|
|
19
21
|
def ask
|
|
20
|
-
|
|
22
|
+
error_message = nil
|
|
23
|
+
validation_checked = false
|
|
21
24
|
|
|
22
25
|
Internal::Terminal.open do |session|
|
|
23
26
|
loop do
|
|
24
|
-
session.render(render(width: session.width, error_message:
|
|
27
|
+
session.render(render(width: session.width, error_message: error_message))
|
|
25
28
|
|
|
26
29
|
case session.read_key
|
|
27
30
|
when :enter
|
|
28
|
-
|
|
29
|
-
|
|
31
|
+
unless validation_checked
|
|
32
|
+
error_message = validation_message
|
|
33
|
+
validation_checked = true
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
next if error_message
|
|
30
37
|
|
|
31
38
|
session.render(submitted_render(width: session.width))
|
|
32
39
|
return @value
|
|
33
40
|
when :left, "h"
|
|
34
41
|
previous_value = @value
|
|
35
42
|
@value = true
|
|
36
|
-
|
|
43
|
+
|
|
44
|
+
if @value != previous_value
|
|
45
|
+
error_message = validation_message
|
|
46
|
+
validation_checked = true
|
|
47
|
+
end
|
|
37
48
|
when :right, "l"
|
|
38
49
|
previous_value = @value
|
|
39
50
|
@value = false
|
|
40
|
-
|
|
51
|
+
|
|
52
|
+
if @value != previous_value
|
|
53
|
+
error_message = validation_message
|
|
54
|
+
validation_checked = true
|
|
55
|
+
end
|
|
41
56
|
end
|
|
42
57
|
end
|
|
43
58
|
end
|
|
@@ -84,8 +99,8 @@ module AskTTY
|
|
|
84
99
|
["enter (submit)", "left/right (select option)"]
|
|
85
100
|
end
|
|
86
101
|
|
|
87
|
-
def validation_message
|
|
88
|
-
Internal::Validation.message_for(@value, @validator
|
|
102
|
+
def validation_message
|
|
103
|
+
Internal::Validation.message_for(@value, @validator)
|
|
89
104
|
end
|
|
90
105
|
end
|
|
91
106
|
end
|
|
@@ -3,17 +3,16 @@
|
|
|
3
3
|
module AskTTY
|
|
4
4
|
class InputPrompt
|
|
5
5
|
def self.ask(title:, details: nil, placeholder: nil, value: nil, &validator)
|
|
6
|
-
new(
|
|
6
|
+
Internal::TextInputPrompt.new(
|
|
7
|
+
title: title,
|
|
8
|
+
details: details,
|
|
9
|
+
placeholder: placeholder,
|
|
10
|
+
value: value,
|
|
11
|
+
validator: validator,
|
|
12
|
+
multiline: false
|
|
13
|
+
).ask
|
|
7
14
|
end
|
|
8
15
|
|
|
9
|
-
|
|
10
|
-
@prompt = Internal::TextInputPrompt.new(
|
|
11
|
-
title: title, details: details, placeholder: placeholder, value: value, validator: validator, multiline: false
|
|
12
|
-
)
|
|
13
|
-
end
|
|
14
|
-
|
|
15
|
-
def ask
|
|
16
|
-
@prompt.ask
|
|
17
|
-
end
|
|
16
|
+
private_class_method :new
|
|
18
17
|
end
|
|
19
18
|
end
|
|
@@ -6,32 +6,41 @@ module AskTTY
|
|
|
6
6
|
new(title: title, details: details, options: options, values: values, validator: validator).ask
|
|
7
7
|
end
|
|
8
8
|
|
|
9
|
+
private_class_method :new
|
|
10
|
+
|
|
9
11
|
def initialize(title:, options:, details: nil, values: nil, validator: nil)
|
|
10
12
|
@title = title.to_s
|
|
11
13
|
@details = details&.to_s
|
|
12
14
|
@options = Internal::Options.normalize(options)
|
|
13
|
-
@values = Array(values).uniq
|
|
14
15
|
@validator = validator
|
|
15
16
|
|
|
16
|
-
|
|
17
|
+
@values = Array(values).map do |value|
|
|
18
|
+
option = @options.find { |candidate| candidate.value == value }
|
|
19
|
+
|
|
20
|
+
raise AskTTY::Error, "values contain unknown option values" unless option
|
|
17
21
|
|
|
18
|
-
|
|
19
|
-
|
|
22
|
+
option.value
|
|
23
|
+
end
|
|
20
24
|
|
|
21
25
|
@index = first_selected_index || 0
|
|
22
26
|
end
|
|
23
27
|
|
|
24
28
|
def ask
|
|
25
|
-
|
|
29
|
+
error_message = nil
|
|
30
|
+
validation_checked = false
|
|
26
31
|
|
|
27
32
|
Internal::Terminal.open do |session|
|
|
28
33
|
loop do
|
|
29
|
-
session.render(render(width: session.width, error_message:
|
|
34
|
+
session.render(render(width: session.width, error_message: error_message))
|
|
30
35
|
|
|
31
36
|
case session.read_key
|
|
32
37
|
when :enter
|
|
33
|
-
|
|
34
|
-
|
|
38
|
+
unless validation_checked
|
|
39
|
+
error_message = validation_message
|
|
40
|
+
validation_checked = true
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
next if error_message
|
|
35
44
|
|
|
36
45
|
session.render(submitted_render(width: session.width))
|
|
37
46
|
return selected_results
|
|
@@ -40,9 +49,9 @@ module AskTTY
|
|
|
40
49
|
when :down, "j"
|
|
41
50
|
move(1)
|
|
42
51
|
when " "
|
|
43
|
-
previous_results = selected_results
|
|
44
52
|
toggle_current
|
|
45
|
-
|
|
53
|
+
error_message = validation_message
|
|
54
|
+
validation_checked = true
|
|
46
55
|
end
|
|
47
56
|
end
|
|
48
57
|
end
|
|
@@ -68,10 +77,12 @@ module AskTTY
|
|
|
68
77
|
@options.each_with_index.flat_map do |option, index|
|
|
69
78
|
cursor = index == @index ? Internal::ANSIStyle.prompt("> ") : " "
|
|
70
79
|
selected = @values.include?(option.value)
|
|
71
|
-
|
|
80
|
+
marker = selected ? Internal::ANSIStyle.selected("[•] ") : Internal::ANSIStyle.text("[ ] ")
|
|
72
81
|
style = selected ? Internal::ANSIStyle.method(:selected) : Internal::ANSIStyle.method(:text)
|
|
73
82
|
|
|
74
|
-
|
|
83
|
+
Internal::Rendering.prefixed_lines(
|
|
84
|
+
option.label, prefix: cursor + marker, width: content_width, style: style
|
|
85
|
+
)
|
|
75
86
|
end
|
|
76
87
|
end
|
|
77
88
|
|
|
@@ -105,18 +116,8 @@ module AskTTY
|
|
|
105
116
|
end
|
|
106
117
|
end
|
|
107
118
|
|
|
108
|
-
def validation_message
|
|
109
|
-
Internal::Validation.message_for(selected_results, @validator
|
|
110
|
-
end
|
|
111
|
-
|
|
112
|
-
def wrap_option(label, cursor:, prefix:, width:, &style)
|
|
113
|
-
prefix_width = Internal::Rendering.display_width(cursor + prefix)
|
|
114
|
-
wrapped = Internal::Rendering.wrap(label, [width - prefix_width, 1].max)
|
|
115
|
-
|
|
116
|
-
wrapped.each_with_index.map do |line, index|
|
|
117
|
-
current_prefix = index.zero? ? cursor + prefix : (" " * prefix_width)
|
|
118
|
-
current_prefix + style.call(line)
|
|
119
|
-
end
|
|
119
|
+
def validation_message
|
|
120
|
+
Internal::Validation.message_for(selected_results, @validator)
|
|
120
121
|
end
|
|
121
122
|
end
|
|
122
123
|
end
|
|
@@ -2,12 +2,16 @@
|
|
|
2
2
|
|
|
3
3
|
module AskTTY
|
|
4
4
|
class SelectPrompt
|
|
5
|
-
UNSET = Object.new
|
|
5
|
+
UNSET = Object.new.freeze
|
|
6
|
+
|
|
7
|
+
private_constant :UNSET
|
|
6
8
|
|
|
7
9
|
def self.ask(title:, options:, details: nil, value: UNSET, &validator)
|
|
8
10
|
new(title: title, details: details, options: options, value: value, validator: validator).ask
|
|
9
11
|
end
|
|
10
12
|
|
|
13
|
+
private_class_method :new
|
|
14
|
+
|
|
11
15
|
def initialize(title:, options:, details: nil, value: UNSET, validator: nil)
|
|
12
16
|
@title = title.to_s
|
|
13
17
|
@details = details&.to_s
|
|
@@ -17,27 +21,40 @@ module AskTTY
|
|
|
17
21
|
end
|
|
18
22
|
|
|
19
23
|
def ask
|
|
20
|
-
|
|
24
|
+
error_message = nil
|
|
25
|
+
validation_checked = false
|
|
21
26
|
|
|
22
27
|
Internal::Terminal.open do |session|
|
|
23
28
|
loop do
|
|
24
|
-
session.render(render(width: session.width, error_message:
|
|
29
|
+
session.render(render(width: session.width, error_message: error_message))
|
|
25
30
|
|
|
26
31
|
case session.read_key
|
|
27
32
|
when :enter
|
|
28
|
-
|
|
29
|
-
|
|
33
|
+
unless validation_checked
|
|
34
|
+
error_message = validation_message
|
|
35
|
+
validation_checked = true
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
next if error_message
|
|
30
39
|
|
|
31
40
|
session.render(submitted_render(width: session.width))
|
|
32
41
|
return selected_option.value
|
|
33
42
|
when :up, "k"
|
|
34
43
|
previous_value = selected_option.value
|
|
35
44
|
move(-1)
|
|
36
|
-
|
|
45
|
+
|
|
46
|
+
if selected_option.value != previous_value
|
|
47
|
+
error_message = validation_message
|
|
48
|
+
validation_checked = true
|
|
49
|
+
end
|
|
37
50
|
when :down, "j"
|
|
38
51
|
previous_value = selected_option.value
|
|
39
52
|
move(1)
|
|
40
|
-
|
|
53
|
+
|
|
54
|
+
if selected_option.value != previous_value
|
|
55
|
+
error_message = validation_message
|
|
56
|
+
validation_checked = true
|
|
57
|
+
end
|
|
41
58
|
end
|
|
42
59
|
end
|
|
43
60
|
end
|
|
@@ -62,7 +79,9 @@ module AskTTY
|
|
|
62
79
|
prefix = index == @index ? Internal::ANSIStyle.prompt("> ") : " "
|
|
63
80
|
style = index == @index ? Internal::ANSIStyle.method(:selected) : Internal::ANSIStyle.method(:text)
|
|
64
81
|
|
|
65
|
-
|
|
82
|
+
Internal::Rendering.prefixed_lines(
|
|
83
|
+
option.label, prefix: prefix, width: content_width, style: style
|
|
84
|
+
)
|
|
66
85
|
end
|
|
67
86
|
end
|
|
68
87
|
|
|
@@ -85,17 +104,8 @@ module AskTTY
|
|
|
85
104
|
@options[@index]
|
|
86
105
|
end
|
|
87
106
|
|
|
88
|
-
def validation_message
|
|
89
|
-
Internal::Validation.message_for(selected_option.value, @validator
|
|
90
|
-
end
|
|
91
|
-
|
|
92
|
-
def wrap_option(label, prefix:, width:, &style)
|
|
93
|
-
wrapped = Internal::Rendering.wrap(label, [width - 2, 1].max)
|
|
94
|
-
|
|
95
|
-
wrapped.each_with_index.map do |line, index|
|
|
96
|
-
current_prefix = index.zero? ? prefix : " "
|
|
97
|
-
current_prefix + style.call(line)
|
|
98
|
-
end
|
|
107
|
+
def validation_message
|
|
108
|
+
Internal::Validation.message_for(selected_option.value, @validator)
|
|
99
109
|
end
|
|
100
110
|
end
|
|
101
111
|
end
|
|
@@ -3,17 +3,16 @@
|
|
|
3
3
|
module AskTTY
|
|
4
4
|
class TextPrompt
|
|
5
5
|
def self.ask(title:, details: nil, placeholder: nil, value: nil, &validator)
|
|
6
|
-
new(
|
|
6
|
+
Internal::TextInputPrompt.new(
|
|
7
|
+
title: title,
|
|
8
|
+
details: details,
|
|
9
|
+
placeholder: placeholder,
|
|
10
|
+
value: value,
|
|
11
|
+
validator: validator,
|
|
12
|
+
multiline: true
|
|
13
|
+
).ask
|
|
7
14
|
end
|
|
8
15
|
|
|
9
|
-
|
|
10
|
-
@prompt = Internal::TextInputPrompt.new(
|
|
11
|
-
title: title, details: details, placeholder: placeholder, value: value, validator: validator, multiline: true
|
|
12
|
-
)
|
|
13
|
-
end
|
|
14
|
-
|
|
15
|
-
def ask
|
|
16
|
-
@prompt.ask
|
|
17
|
-
end
|
|
16
|
+
private_class_method :new
|
|
18
17
|
end
|
|
19
18
|
end
|
data/lib/asktty/version.rb
CHANGED
data/lib/asktty.rb
CHANGED
|
@@ -1,6 +1,11 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
require_relative "asktty/version"
|
|
4
|
+
|
|
5
|
+
module AskTTY
|
|
6
|
+
class Error < StandardError; end
|
|
7
|
+
end
|
|
8
|
+
|
|
4
9
|
require_relative "asktty/internal/ansi_style"
|
|
5
10
|
require_relative "asktty/internal/options"
|
|
6
11
|
require_relative "asktty/internal/rendering"
|
|
@@ -12,7 +17,3 @@ require_relative "asktty/prompts/text_prompt"
|
|
|
12
17
|
require_relative "asktty/prompts/select_prompt"
|
|
13
18
|
require_relative "asktty/prompts/multi_select_prompt"
|
|
14
19
|
require_relative "asktty/prompts/confirm_prompt"
|
|
15
|
-
|
|
16
|
-
module AskTTY
|
|
17
|
-
class Error < StandardError; end
|
|
18
|
-
end
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: asktty
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version:
|
|
4
|
+
version: 2.0.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Vlad Suchy
|
|
@@ -65,7 +65,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
65
65
|
- !ruby/object:Gem::Version
|
|
66
66
|
version: '0'
|
|
67
67
|
requirements: []
|
|
68
|
-
rubygems_version:
|
|
68
|
+
rubygems_version: 3.7.2
|
|
69
69
|
specification_version: 4
|
|
70
70
|
summary: Terminal prompts for Ruby.
|
|
71
71
|
test_files: []
|