austb-tty-prompt 0.13.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 +7 -0
- data/.gitignore +14 -0
- data/.rspec +3 -0
- data/.travis.yml +25 -0
- data/CHANGELOG.md +218 -0
- data/CODE_OF_CONDUCT.md +49 -0
- data/Gemfile +19 -0
- data/LICENSE.txt +22 -0
- data/README.md +1132 -0
- data/Rakefile +8 -0
- data/appveyor.yml +23 -0
- data/benchmarks/speed.rb +27 -0
- data/examples/ask.rb +15 -0
- data/examples/collect.rb +19 -0
- data/examples/echo.rb +11 -0
- data/examples/enum.rb +8 -0
- data/examples/enum_paged.rb +9 -0
- data/examples/enum_select.rb +7 -0
- data/examples/expand.rb +29 -0
- data/examples/in.rb +9 -0
- data/examples/inputs.rb +10 -0
- data/examples/key_events.rb +11 -0
- data/examples/keypress.rb +9 -0
- data/examples/mask.rb +13 -0
- data/examples/multi_select.rb +8 -0
- data/examples/multi_select_paged.rb +9 -0
- data/examples/multiline.rb +9 -0
- data/examples/pause.rb +7 -0
- data/examples/select.rb +18 -0
- data/examples/select_paginated.rb +9 -0
- data/examples/slider.rb +6 -0
- data/examples/validation.rb +9 -0
- data/examples/yes_no.rb +7 -0
- data/lib/tty-prompt.rb +4 -0
- data/lib/tty/prompt.rb +535 -0
- data/lib/tty/prompt/answers_collector.rb +59 -0
- data/lib/tty/prompt/choice.rb +90 -0
- data/lib/tty/prompt/choices.rb +110 -0
- data/lib/tty/prompt/confirm_question.rb +129 -0
- data/lib/tty/prompt/converter_dsl.rb +22 -0
- data/lib/tty/prompt/converter_registry.rb +64 -0
- data/lib/tty/prompt/converters.rb +77 -0
- data/lib/tty/prompt/distance.rb +49 -0
- data/lib/tty/prompt/enum_list.rb +337 -0
- data/lib/tty/prompt/enum_paginator.rb +56 -0
- data/lib/tty/prompt/evaluator.rb +29 -0
- data/lib/tty/prompt/expander.rb +292 -0
- data/lib/tty/prompt/keypress.rb +94 -0
- data/lib/tty/prompt/list.rb +317 -0
- data/lib/tty/prompt/mask_question.rb +91 -0
- data/lib/tty/prompt/multi_list.rb +108 -0
- data/lib/tty/prompt/multiline.rb +71 -0
- data/lib/tty/prompt/paginator.rb +88 -0
- data/lib/tty/prompt/question.rb +333 -0
- data/lib/tty/prompt/question/checks.rb +87 -0
- data/lib/tty/prompt/question/modifier.rb +94 -0
- data/lib/tty/prompt/question/validation.rb +72 -0
- data/lib/tty/prompt/reader.rb +352 -0
- data/lib/tty/prompt/reader/codes.rb +121 -0
- data/lib/tty/prompt/reader/console.rb +57 -0
- data/lib/tty/prompt/reader/history.rb +145 -0
- data/lib/tty/prompt/reader/key_event.rb +91 -0
- data/lib/tty/prompt/reader/line.rb +162 -0
- data/lib/tty/prompt/reader/mode.rb +44 -0
- data/lib/tty/prompt/reader/win_api.rb +29 -0
- data/lib/tty/prompt/reader/win_console.rb +53 -0
- data/lib/tty/prompt/result.rb +42 -0
- data/lib/tty/prompt/slider.rb +182 -0
- data/lib/tty/prompt/statement.rb +55 -0
- data/lib/tty/prompt/suggestion.rb +115 -0
- data/lib/tty/prompt/symbols.rb +61 -0
- data/lib/tty/prompt/timeout.rb +69 -0
- data/lib/tty/prompt/utils.rb +44 -0
- data/lib/tty/prompt/version.rb +7 -0
- data/lib/tty/test_prompt.rb +20 -0
- data/tasks/console.rake +11 -0
- data/tasks/coverage.rake +11 -0
- data/tasks/spec.rake +29 -0
- data/tty-prompt.gemspec +32 -0
- metadata +243 -0
@@ -0,0 +1,87 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
module TTY
|
4
|
+
class Prompt
|
5
|
+
class Question
|
6
|
+
module Checks
|
7
|
+
# Check if modifications are applicable
|
8
|
+
class CheckModifier
|
9
|
+
def self.call(question, value)
|
10
|
+
if !question.modifier.nil? || question.modifier
|
11
|
+
[Modifier.new(question.modifier).apply_to(value)]
|
12
|
+
else
|
13
|
+
[value]
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
# Check if value is within range
|
19
|
+
class CheckRange
|
20
|
+
def self.float?(value)
|
21
|
+
!/[-+]?(\d*[.])?\d+/.match(value.to_s).nil?
|
22
|
+
end
|
23
|
+
|
24
|
+
def self.int?(value)
|
25
|
+
!/^[-+]?\d+$/.match(value.to_s).nil?
|
26
|
+
end
|
27
|
+
|
28
|
+
def self.cast(value)
|
29
|
+
if float?(value)
|
30
|
+
value.to_f
|
31
|
+
elsif int?(value)
|
32
|
+
value.to_i
|
33
|
+
else
|
34
|
+
value
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
def self.call(question, value)
|
39
|
+
if !question.in? ||
|
40
|
+
(question.in? && question.in.include?(cast(value)))
|
41
|
+
[value]
|
42
|
+
else
|
43
|
+
tokens = {value: value, in: question.in}
|
44
|
+
[value, question.message_for(:range?, tokens)]
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
# Check if input requires validation
|
50
|
+
class CheckValidation
|
51
|
+
def self.call(question, value)
|
52
|
+
if !question.validation? || (question.required? && value.nil?) ||
|
53
|
+
(question.validation? &&
|
54
|
+
Validation.new(question.validation).call(value))
|
55
|
+
[value]
|
56
|
+
else
|
57
|
+
tokens = {valid: question.validation.inspect}
|
58
|
+
[value, question.message_for(:valid?, tokens)]
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
# Check if default value provided
|
64
|
+
class CheckDefault
|
65
|
+
def self.call(question, value)
|
66
|
+
if value.nil? && question.default?
|
67
|
+
[question.default]
|
68
|
+
else
|
69
|
+
[value]
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
# Check if input is required
|
75
|
+
class CheckRequired
|
76
|
+
def self.call(question, value)
|
77
|
+
if question.required? && !question.default? && value.nil?
|
78
|
+
[value, question.message_for(:required?)]
|
79
|
+
else
|
80
|
+
[value]
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|
84
|
+
end # Checks
|
85
|
+
end # Question
|
86
|
+
end # Prompt
|
87
|
+
end # TTY
|
@@ -0,0 +1,94 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
module TTY
|
4
|
+
class Prompt
|
5
|
+
class Question
|
6
|
+
# A class representing String modifications.
|
7
|
+
class Modifier
|
8
|
+
attr_reader :modifiers
|
9
|
+
|
10
|
+
# Initialize a Modifier
|
11
|
+
#
|
12
|
+
# @api public
|
13
|
+
def initialize(modifiers)
|
14
|
+
@modifiers = modifiers
|
15
|
+
end
|
16
|
+
|
17
|
+
# Change supplied value according to the given string transformation.
|
18
|
+
# Valid settings are:
|
19
|
+
#
|
20
|
+
# @param [String] value
|
21
|
+
# the string to be modified
|
22
|
+
#
|
23
|
+
# @return [String]
|
24
|
+
#
|
25
|
+
# @api private
|
26
|
+
def apply_to(value)
|
27
|
+
modifiers.reduce(value) do |result, mod|
|
28
|
+
result = Modifier.letter_case(mod, result)
|
29
|
+
Modifier.whitespace(mod, result)
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
# Changes letter casing in a string according to valid modifications.
|
34
|
+
# For invalid modification option the string is preserved.
|
35
|
+
#
|
36
|
+
# @param [Symbol] mod
|
37
|
+
# the modification to change the string
|
38
|
+
#
|
39
|
+
# @option mod [Symbol] :up change to upper case
|
40
|
+
# @option mod [Symbol] :upcase change to upper case
|
41
|
+
# @option mod [Symbol] :uppercase change to upper case
|
42
|
+
# @option mod [Symbol] :down change to lower case
|
43
|
+
# @option mod [Symbol] :downcase change to lower case
|
44
|
+
# @option mod [Symbol] :capitalize change all words to start
|
45
|
+
# with uppercase case letter
|
46
|
+
#
|
47
|
+
# @return [String]
|
48
|
+
#
|
49
|
+
# @api public
|
50
|
+
def self.letter_case(mod, value)
|
51
|
+
return value unless value.is_a?(String)
|
52
|
+
case mod
|
53
|
+
when :up, :upcase, :uppercase
|
54
|
+
value.upcase
|
55
|
+
when :down, :downcase, :lowercase
|
56
|
+
value.downcase
|
57
|
+
when :capitalize
|
58
|
+
value.capitalize
|
59
|
+
else
|
60
|
+
value
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
# Changes whitespace in a string according to valid modifications.
|
65
|
+
#
|
66
|
+
# @param [Symbol] mod
|
67
|
+
# the modification to change the string
|
68
|
+
#
|
69
|
+
# @option mod [String] :trim, :strip
|
70
|
+
# remove whitespace for the start and end
|
71
|
+
# @option mod [String] :chomp remove record separator from the end
|
72
|
+
# @option mod [String] :collapse remove any duplicate whitespace
|
73
|
+
# @option mod [String] :remove remove all whitespace
|
74
|
+
#
|
75
|
+
# @api public
|
76
|
+
def self.whitespace(mod, value)
|
77
|
+
return value unless value.is_a?(String)
|
78
|
+
case mod
|
79
|
+
when :trim, :strip
|
80
|
+
value.strip
|
81
|
+
when :chomp
|
82
|
+
value.chomp
|
83
|
+
when :collapse
|
84
|
+
value.gsub(/\s+/, ' ')
|
85
|
+
when :remove
|
86
|
+
value.gsub(/\s+/, '')
|
87
|
+
else
|
88
|
+
value
|
89
|
+
end
|
90
|
+
end
|
91
|
+
end # Modifier
|
92
|
+
end # Question
|
93
|
+
end # Prompt
|
94
|
+
end # TTY
|
@@ -0,0 +1,72 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
module TTY
|
4
|
+
class Prompt
|
5
|
+
class Question
|
6
|
+
# A class representing question validation.
|
7
|
+
class Validation
|
8
|
+
# Available validator names
|
9
|
+
VALIDATORS = {
|
10
|
+
email: /^[a-z0-9._%+-]+@([a-z0-9-]+\.)+[a-z]{2,6}$/i
|
11
|
+
}.freeze
|
12
|
+
|
13
|
+
attr_reader :pattern
|
14
|
+
|
15
|
+
# Initialize a Validation
|
16
|
+
#
|
17
|
+
# @param [Object] pattern
|
18
|
+
#
|
19
|
+
# @return [undefined]
|
20
|
+
#
|
21
|
+
# @api private
|
22
|
+
def initialize(pattern)
|
23
|
+
@pattern = coerce(pattern)
|
24
|
+
end
|
25
|
+
|
26
|
+
# Convert validation into known type.
|
27
|
+
#
|
28
|
+
# @param [Object] pattern
|
29
|
+
#
|
30
|
+
# @raise [TTY::ValidationCoercion]
|
31
|
+
# raised when failed to convert validation
|
32
|
+
#
|
33
|
+
# @api private
|
34
|
+
def coerce(pattern)
|
35
|
+
case pattern
|
36
|
+
when String, Symbol, Proc
|
37
|
+
pattern
|
38
|
+
when Regexp
|
39
|
+
Regexp.new(pattern.to_s)
|
40
|
+
else
|
41
|
+
raise ValidationCoercion, "Wrong type, got #{pattern.class}"
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
# Test if the input passes the validation
|
46
|
+
#
|
47
|
+
# @example
|
48
|
+
# Validation.new(/pattern/)
|
49
|
+
# validation.call(input) # => true
|
50
|
+
#
|
51
|
+
# @param [Object] input
|
52
|
+
# the input to validate
|
53
|
+
#
|
54
|
+
# @return [Boolean]
|
55
|
+
#
|
56
|
+
# @api public
|
57
|
+
def call(input)
|
58
|
+
if pattern.is_a?(String) || pattern.is_a?(Symbol)
|
59
|
+
VALIDATORS.key?(pattern.to_sym)
|
60
|
+
!VALIDATORS[pattern.to_sym].match(input).nil?
|
61
|
+
elsif pattern.is_a?(Regexp)
|
62
|
+
!pattern.match(input).nil?
|
63
|
+
elsif pattern.is_a?(Proc)
|
64
|
+
result = pattern.call(input)
|
65
|
+
result.nil? ? false : result
|
66
|
+
else false
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end # Validation
|
70
|
+
end # Question
|
71
|
+
end # Prompt
|
72
|
+
end # TTY
|
@@ -0,0 +1,352 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
require 'wisper'
|
4
|
+
require 'rbconfig'
|
5
|
+
|
6
|
+
require_relative 'reader/history'
|
7
|
+
require_relative 'reader/line'
|
8
|
+
require_relative 'reader/key_event'
|
9
|
+
require_relative 'reader/console'
|
10
|
+
require_relative 'reader/win_console'
|
11
|
+
|
12
|
+
module TTY
|
13
|
+
# A class responsible for shell prompt interactions.
|
14
|
+
class Prompt
|
15
|
+
# A class responsible for reading character input from STDIN
|
16
|
+
#
|
17
|
+
# Used internally to provide key and line reading functionality
|
18
|
+
#
|
19
|
+
# @api private
|
20
|
+
class Reader
|
21
|
+
include Wisper::Publisher
|
22
|
+
|
23
|
+
# Raised when the user hits the interrupt key(Control-C)
|
24
|
+
#
|
25
|
+
# @api public
|
26
|
+
InputInterrupt = Class.new(StandardError)
|
27
|
+
|
28
|
+
attr_reader :input
|
29
|
+
|
30
|
+
attr_reader :output
|
31
|
+
|
32
|
+
attr_reader :env
|
33
|
+
|
34
|
+
attr_reader :track_history
|
35
|
+
alias track_history? track_history
|
36
|
+
|
37
|
+
attr_reader :console
|
38
|
+
|
39
|
+
# Key codes
|
40
|
+
CARRIAGE_RETURN = 13
|
41
|
+
NEWLINE = 10
|
42
|
+
BACKSPACE = 127
|
43
|
+
DELETE = 8
|
44
|
+
|
45
|
+
# Initialize a Reader
|
46
|
+
#
|
47
|
+
# @param [IO] input
|
48
|
+
# the input stream
|
49
|
+
# @param [IO] output
|
50
|
+
# the output stream
|
51
|
+
# @param [Hash] options
|
52
|
+
# @option options [Symbol] :interrupt
|
53
|
+
# handling of Ctrl+C key out of :signal, :exit, :noop
|
54
|
+
# @option options [Boolean] :track_history
|
55
|
+
# disable line history tracking, true by default
|
56
|
+
#
|
57
|
+
# @api public
|
58
|
+
def initialize(input = $stdin, output = $stdout, options = {})
|
59
|
+
@input = input
|
60
|
+
@output = output
|
61
|
+
@interrupt = options.fetch(:interrupt) { :error }
|
62
|
+
@env = options.fetch(:env) { ENV }
|
63
|
+
@track_history = options.fetch(:track_history) { true }
|
64
|
+
@console = select_console(input)
|
65
|
+
@history = History.new do |h|
|
66
|
+
h.duplicates = false
|
67
|
+
h.exclude = proc { |line| line.strip == '' }
|
68
|
+
end
|
69
|
+
@stop = false # gathering input
|
70
|
+
|
71
|
+
subscribe(self)
|
72
|
+
end
|
73
|
+
|
74
|
+
# Select appropriate console
|
75
|
+
#
|
76
|
+
# @api private
|
77
|
+
def select_console(input)
|
78
|
+
if windows? && !env['TTY_TEST']
|
79
|
+
WinConsole.new(input)
|
80
|
+
else
|
81
|
+
Console.new(input)
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
# Get input in unbuffered mode.
|
86
|
+
#
|
87
|
+
# @example
|
88
|
+
# unbufferred do
|
89
|
+
# ...
|
90
|
+
# end
|
91
|
+
#
|
92
|
+
# @api public
|
93
|
+
def unbufferred(&block)
|
94
|
+
bufferring = output.sync
|
95
|
+
# Immediately flush output
|
96
|
+
output.sync = true
|
97
|
+
block[] if block_given?
|
98
|
+
ensure
|
99
|
+
output.sync = bufferring
|
100
|
+
end
|
101
|
+
|
102
|
+
# Read a keypress including invisible multibyte codes
|
103
|
+
# and return a character as a string.
|
104
|
+
# Nothing is echoed to the console. This call will block for a
|
105
|
+
# single keypress, but will not wait for Enter to be pressed.
|
106
|
+
#
|
107
|
+
# @param [Hash[Symbol]] options
|
108
|
+
# @option options [Boolean] echo
|
109
|
+
# whether to echo chars back or not, defaults to false
|
110
|
+
# @option options [Boolean] raw
|
111
|
+
# whenther raw mode enabled, defaults to true
|
112
|
+
#
|
113
|
+
# @return [String]
|
114
|
+
#
|
115
|
+
# @api public
|
116
|
+
def read_keypress(options = {})
|
117
|
+
opts = { echo: false, raw: true }.merge(options)
|
118
|
+
codes = unbufferred { get_codes(opts) }
|
119
|
+
char = codes ? codes.pack('U*') : nil
|
120
|
+
|
121
|
+
trigger_key_event(char) if char
|
122
|
+
handle_interrupt if char == console.keys[:ctrl_c]
|
123
|
+
char
|
124
|
+
end
|
125
|
+
alias read_char read_keypress
|
126
|
+
|
127
|
+
# Get input code points
|
128
|
+
#
|
129
|
+
# @param [Hash[Symbol]] options
|
130
|
+
# @param [Array[Integer]] codes
|
131
|
+
#
|
132
|
+
# @return [Array[Integer]]
|
133
|
+
#
|
134
|
+
# @api private
|
135
|
+
def get_codes(options = {}, codes = [])
|
136
|
+
opts = { echo: true, raw: false }.merge(options)
|
137
|
+
char = console.get_char(opts)
|
138
|
+
return if char.nil?
|
139
|
+
codes << char.ord
|
140
|
+
|
141
|
+
condition = proc { |escape|
|
142
|
+
(codes - escape).empty? ||
|
143
|
+
(escape - codes).empty? &&
|
144
|
+
!(64..126).include?(codes.last)
|
145
|
+
}
|
146
|
+
|
147
|
+
while console.escape_codes.any?(&condition)
|
148
|
+
get_codes(options, codes)
|
149
|
+
end
|
150
|
+
codes
|
151
|
+
end
|
152
|
+
|
153
|
+
# Get a single line from STDIN. Each key pressed is echoed
|
154
|
+
# back to the shell. The input terminates when enter or
|
155
|
+
# return key is pressed.
|
156
|
+
#
|
157
|
+
# @param [String] prompt
|
158
|
+
# the prompt to display before input
|
159
|
+
#
|
160
|
+
# @param [Boolean] echo
|
161
|
+
# if true echo back characters, output nothing otherwise
|
162
|
+
#
|
163
|
+
# @return [String]
|
164
|
+
#
|
165
|
+
# @api public
|
166
|
+
def read_line(*args)
|
167
|
+
options = args.last.respond_to?(:to_hash) ? args.pop : {}
|
168
|
+
prompt = args.empty? ? '' : args.pop
|
169
|
+
opts = { echo: true, raw: true }.merge(options)
|
170
|
+
line = Line.new('')
|
171
|
+
ctrls = console.keys.keys.grep(/ctrl/)
|
172
|
+
clear_line = "\e[2K\e[1G"
|
173
|
+
|
174
|
+
while (codes = unbufferred { get_codes(opts) }) && (code = codes[0])
|
175
|
+
char = codes.pack('U*')
|
176
|
+
trigger_key_event(char)
|
177
|
+
|
178
|
+
if console.keys[:backspace] == char || BACKSPACE == code
|
179
|
+
next if line.start?
|
180
|
+
line.left
|
181
|
+
line.delete
|
182
|
+
elsif console.keys[:delete] == char || DELETE == code
|
183
|
+
line.delete
|
184
|
+
elsif [console.keys[:ctrl_d],
|
185
|
+
console.keys[:ctrl_z]].include?(char)
|
186
|
+
break
|
187
|
+
elsif console.keys[:ctrl_c] == char
|
188
|
+
handle_interrupt
|
189
|
+
elsif ctrls.include?(console.keys.key(char))
|
190
|
+
# skip
|
191
|
+
elsif console.keys[:up] == char
|
192
|
+
next unless history_previous?
|
193
|
+
line.replace(history_previous)
|
194
|
+
elsif console.keys[:down] == char
|
195
|
+
line.replace(history_next? ? history_next : '')
|
196
|
+
elsif console.keys[:left] == char
|
197
|
+
line.left
|
198
|
+
elsif console.keys[:right] == char
|
199
|
+
line.right
|
200
|
+
else
|
201
|
+
if opts[:raw] && code == CARRIAGE_RETURN
|
202
|
+
char = "\n"
|
203
|
+
line.move_to_end
|
204
|
+
end
|
205
|
+
line.insert(char)
|
206
|
+
end
|
207
|
+
|
208
|
+
if opts[:raw] && opts[:echo]
|
209
|
+
output.print(clear_line)
|
210
|
+
output.print(prompt + line.to_s)
|
211
|
+
if char == "\n"
|
212
|
+
line.move_to_start
|
213
|
+
elsif !line.end?
|
214
|
+
output.print("\e[#{line.size - line.cursor}D")
|
215
|
+
end
|
216
|
+
end
|
217
|
+
|
218
|
+
break if (code == CARRIAGE_RETURN || code == NEWLINE)
|
219
|
+
|
220
|
+
if (console.keys[:backspace] == char || BACKSPACE == code) && opts[:echo]
|
221
|
+
if opts[:raw]
|
222
|
+
output.print("\e[1X") unless line.start?
|
223
|
+
else
|
224
|
+
output.print(?\s + (line.start? ? '' : ?\b))
|
225
|
+
end
|
226
|
+
end
|
227
|
+
end
|
228
|
+
add_to_history(line.to_s.rstrip) if track_history?
|
229
|
+
line.to_s
|
230
|
+
end
|
231
|
+
|
232
|
+
# Read multiple lines and return them in an array.
|
233
|
+
# Skip empty lines in the returned lines array.
|
234
|
+
# The input gathering is terminated by Ctrl+d or Ctrl+z.
|
235
|
+
#
|
236
|
+
# @param [String] prompt
|
237
|
+
# the prompt displayed before the input
|
238
|
+
#
|
239
|
+
# @yield [String] line
|
240
|
+
#
|
241
|
+
# @return [Array[String]]
|
242
|
+
#
|
243
|
+
# @api public
|
244
|
+
def read_multiline(prompt = '')
|
245
|
+
@stop = false
|
246
|
+
lines = []
|
247
|
+
loop do
|
248
|
+
line = read_line(prompt)
|
249
|
+
break if !line || line == ''
|
250
|
+
next if line !~ /\S/ && !@stop
|
251
|
+
if block_given?
|
252
|
+
yield(line) unless line.to_s.empty?
|
253
|
+
else
|
254
|
+
lines << line unless line.to_s.empty?
|
255
|
+
end
|
256
|
+
break if @stop
|
257
|
+
end
|
258
|
+
lines
|
259
|
+
end
|
260
|
+
alias read_lines read_multiline
|
261
|
+
|
262
|
+
# Expose event broadcasting
|
263
|
+
#
|
264
|
+
# @api public
|
265
|
+
def trigger(event, *args)
|
266
|
+
publish(event, *args)
|
267
|
+
end
|
268
|
+
|
269
|
+
# Capture Ctrl+d and Ctrl+z key events
|
270
|
+
#
|
271
|
+
# @api private
|
272
|
+
def keyctrl_d(*)
|
273
|
+
@stop = true
|
274
|
+
end
|
275
|
+
alias keyctrl_z keyctrl_d
|
276
|
+
|
277
|
+
def add_to_history(line)
|
278
|
+
@history.push(line)
|
279
|
+
end
|
280
|
+
|
281
|
+
def history_next?
|
282
|
+
@history.next?
|
283
|
+
end
|
284
|
+
|
285
|
+
def history_next
|
286
|
+
@history.next
|
287
|
+
@history.get
|
288
|
+
end
|
289
|
+
|
290
|
+
def history_previous?
|
291
|
+
@history.previous?
|
292
|
+
end
|
293
|
+
|
294
|
+
def history_previous
|
295
|
+
line = @history.get
|
296
|
+
@history.previous
|
297
|
+
line
|
298
|
+
end
|
299
|
+
|
300
|
+
# Inspect class name and public attributes
|
301
|
+
# @return [String]
|
302
|
+
#
|
303
|
+
# @api public
|
304
|
+
def inspect
|
305
|
+
"#<#{self.class}: @input=#{input}, @output=#{output}>"
|
306
|
+
end
|
307
|
+
|
308
|
+
private
|
309
|
+
|
310
|
+
# Publish event
|
311
|
+
#
|
312
|
+
# @param [String] char
|
313
|
+
# the key pressed
|
314
|
+
#
|
315
|
+
# @return [nil]
|
316
|
+
#
|
317
|
+
# @api private
|
318
|
+
def trigger_key_event(char)
|
319
|
+
event = KeyEvent.from(console.keys, char)
|
320
|
+
trigger(:"key#{event.key.name}", event) if event.trigger?
|
321
|
+
trigger(:keypress, event)
|
322
|
+
end
|
323
|
+
|
324
|
+
# Handle input interrupt based on provided value
|
325
|
+
#
|
326
|
+
# @api private
|
327
|
+
def handle_interrupt
|
328
|
+
case @interrupt
|
329
|
+
when :signal
|
330
|
+
Process.kill('SIGINT', Process.pid)
|
331
|
+
when :exit
|
332
|
+
exit(130)
|
333
|
+
when Proc
|
334
|
+
@interrupt.call
|
335
|
+
when :noop
|
336
|
+
return
|
337
|
+
else
|
338
|
+
raise InputInterrupt
|
339
|
+
end
|
340
|
+
end
|
341
|
+
|
342
|
+
# Check if Windowz mode
|
343
|
+
#
|
344
|
+
# @return [Boolean]
|
345
|
+
#
|
346
|
+
# @api public
|
347
|
+
def windows?
|
348
|
+
::File::ALT_SEPARATOR == '\\'
|
349
|
+
end
|
350
|
+
end # Reader
|
351
|
+
end # Prompt
|
352
|
+
end # TTY
|