asktty 1.0.0 → 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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 4918843985d1b7a0204a197a2e8499ca7493e171bc4b16307407bab000572c15
4
- data.tar.gz: af5db2494b82ba83bddd0813d40a86dd50833276ba6a00e8df3d2b9b79d1a5e4
3
+ metadata.gz: 7afce23120ce01740dd6c9f0e405080565efd3c22546361508611e8573a051aa
4
+ data.tar.gz: 8c2e75af399a24f63d9230112acbb07152d604f00da0d0cdc95098b9cf48fb31
5
5
  SHA512:
6
- metadata.gz: 0fa70b12b633518b2ab3aac623dada69b67e337401c0d21d466126b0c6a24927921323941357ac81c259e16bc97c2eb75a9a03323af73aa4e0a17f10911bf300
7
- data.tar.gz: 6fba726118b1e937822797c90b84cf840e69c70c77c1f0cc6a7ce772cc063e4ff242559157d031cae16592ddf1b150bafa5a3f35c8bf0d3348677591e6da4e94
6
+ metadata.gz: 2c164a8cd20c460cf1baa0739244155c2f24f862f6b987617245eeac91e1e55938b88d8c109d7ce87df1c05f1181c04e4e983c5ac2febdc8442ecb79364c5d84
7
+ data.tar.gz: 17f6e9e671a4ac84b1cb358f7648c03561b9212f06f4cafa7190f002e4c67f552b03f866236104da19cc772f365b3e147b63fdf03b3acc364a830d4136f275f1
data/README.md CHANGED
@@ -1,18 +1,18 @@
1
1
  # AskTTY
2
2
 
3
- > Note: AskTTY is still under development and has not been released to RubyGems yet.
4
-
5
3
  A Ruby gem for interactive terminal prompts.
6
4
 
5
+ <img src="https://raw.githubusercontent.com/vsuchy/asktty/refs/heads/main/assets/demo/demo.gif" alt="AskTTY Demo" width="600">
6
+
7
7
  ## Installation
8
8
 
9
- Install it globaly:
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 gemfile:
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
- notes = AskTTY::TextPrompt.ask(
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
- drink = AskTTY::SelectPrompt.ask(
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
- toppings = AskTTY::MultiSelectPrompt.ask(
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 an error message when it is not:
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 = Struct.new(:label, :value)
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 not be empty" if options.nil? || options.empty?
13
+ raise AskTTY::Error, "options must be a non-empty array" unless options.is_a?(Array) && !options.empty?
12
14
 
13
- options.map do |option|
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 = option[:label] || option["label"]
19
- has_value = option.key?(:value) || option.key?("value")
20
- value = option[:value] if option.key?(:value)
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
- raise AskTTY::Error, "options must include label and value" unless label && has_value
24
+ value = option[:value]
25
+ raise AskTTY::Error, "option values must be unique" if values.include?(value)
24
26
 
25
- Option.new(label.to_s, value)
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(style_submitted_lines(lines, title_length: graphemes(prefix).length))
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 << " …" if total_width + display_width(" …") <= width
102
+ ellipsis = line.empty? ? "…" : " …"
103
+ line << ellipsis if total_width + display_width(ellipsis) <= width
93
104
  break
94
105
  end
95
106
 
@@ -99,7 +110,13 @@ module AskTTY
99
110
 
100
111
  return "" if line.empty?
101
112
 
102
- ANSIStyle.muted(line)
113
+ line.split(/(\([^)]*\))/).reject(&:empty?).map do |segment|
114
+ if segment.start_with?("(") && segment.end_with?(")")
115
+ ANSIStyle.muted(segment)
116
+ else
117
+ ANSIStyle.text(segment)
118
+ end
119
+ end.join
103
120
  end
104
121
 
105
122
  def content_width(width)
@@ -123,27 +140,6 @@ module AskTTY
123
140
  text.split("\n", -1)
124
141
  end
125
142
 
126
- def style_submitted_lines(lines, title_length:)
127
- remaining_title_length = title_length
128
-
129
- lines.map do |line|
130
- line_graphemes = graphemes(line)
131
-
132
- if remaining_title_length >= line_graphemes.length
133
- remaining_title_length -= line_graphemes.length
134
- ANSIStyle.title(line)
135
- elsif remaining_title_length.positive?
136
- title_text = line_graphemes[0, remaining_title_length].join
137
- value_text = line_graphemes[remaining_title_length..].to_a.join
138
- remaining_title_length = 0
139
-
140
- ANSIStyle.title(title_text) + ANSIStyle.text(value_text)
141
- else
142
- ANSIStyle.text(line)
143
- end
144
- end
145
- end
146
-
147
143
  def wrap_line(line, width)
148
144
  return [""] if line.empty?
149
145
 
@@ -151,6 +147,11 @@ module AskTTY
151
147
  remaining = graphemes(line)
152
148
 
153
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
+
154
155
  segment, last_whitespace_index = take_segment(remaining, width)
155
156
 
156
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(0.01)
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
- validation_active = false
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
- show_cursor: true,
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
- validation_active = true
35
- next if validation_message(value, validation_active)
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
- validation_active = true
47
+ error_message = validation_message(value)
48
+ validation_checked = true
44
49
  when :backspace
45
50
  updated_value = Rendering.chop_grapheme(value)
46
- validation_active ||= updated_value != value
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
- validation_active = true
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:, show_cursor:, error_message: nil)
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, show_cursor: show_cursor)
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:, show_cursor:)
73
- return placeholder_lines(content_width) if @placeholder && value.empty? && show_cursor
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: 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.to_s.split("\n", -1),
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, validation_active)
144
- Validation.message_for(value, @validator, active: validation_active)
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, active:)
9
- return nil unless active && validator
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 an error message"
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
- validation_active = false
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: validation_message(validation_active)))
27
+ session.render(render(width: session.width, error_message: error_message))
25
28
 
26
29
  case session.read_key
27
30
  when :enter
28
- validation_active = true
29
- next if validation_message(validation_active)
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
- validation_active ||= @value != previous_value
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
- validation_active ||= @value != previous_value
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(validation_active)
88
- Internal::Validation.message_for(@value, @validator, active: validation_active)
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(title: title, details: details, placeholder: placeholder, value: value, validator: validator).ask
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
- def initialize(title:, details: nil, placeholder: nil, value: nil, validator: nil)
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
- option_values = Internal::Options.values(@options)
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
- unknown_values = @values - option_values
19
- raise AskTTY::Error, "values contain unknown option values" unless unknown_values.empty?
22
+ option.value
23
+ end
20
24
 
21
25
  @index = first_selected_index || 0
22
26
  end
23
27
 
24
28
  def ask
25
- validation_active = false
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: validation_message(validation_active)))
34
+ session.render(render(width: session.width, error_message: error_message))
30
35
 
31
36
  case session.read_key
32
37
  when :enter
33
- validation_active = true
34
- next if validation_message(validation_active)
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
- validation_active ||= selected_results != previous_results
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
- prefix = selected ? Internal::ANSIStyle.selected("[•] ") : Internal::ANSIStyle.text("[ ] ")
80
+ marker = selected ? Internal::ANSIStyle.selected("[•] ") : Internal::ANSIStyle.text("[ ] ")
72
81
  style = selected ? Internal::ANSIStyle.method(:selected) : Internal::ANSIStyle.method(:text)
73
82
 
74
- wrap_option(option.label, cursor: cursor, prefix: prefix, width: content_width, &style)
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(validation_active)
109
- Internal::Validation.message_for(selected_results, @validator, active: validation_active)
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
- validation_active = false
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: validation_message(validation_active)))
29
+ session.render(render(width: session.width, error_message: error_message))
25
30
 
26
31
  case session.read_key
27
32
  when :enter
28
- validation_active = true
29
- next if validation_message(validation_active)
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
- validation_active ||= selected_option.value != previous_value
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
- validation_active ||= selected_option.value != previous_value
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
- wrap_option(option.label, prefix: prefix, width: content_width, &style)
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(validation_active)
89
- Internal::Validation.message_for(selected_option.value, @validator, active: validation_active)
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(title: title, details: details, placeholder: placeholder, value: value, validator: validator).ask
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
- def initialize(title:, details: nil, placeholder: nil, value: nil, validator: nil)
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
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module AskTTY
4
- VERSION = "1.0.0"
4
+ VERSION = "2.0.0"
5
5
  end
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: 1.0.0
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: 4.0.10
68
+ rubygems_version: 3.7.2
69
69
  specification_version: 4
70
70
  summary: Terminal prompts for Ruby.
71
71
  test_files: []