tty-prompt 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/.travis.yml +9 -6
- data/CHANGELOG.md +40 -3
- data/Gemfile +0 -1
- data/README.md +246 -65
- data/examples/ask.rb +7 -0
- data/examples/echo.rb +7 -0
- data/examples/in.rb +7 -0
- data/examples/mask.rb +9 -0
- data/examples/multi_select.rb +8 -0
- data/examples/select.rb +8 -0
- data/examples/validation.rb +9 -0
- data/examples/yes_no.rb +7 -0
- data/lib/tty-prompt.rb +6 -4
- data/lib/tty/prompt.rb +100 -25
- data/lib/tty/prompt/choice.rb +1 -1
- data/lib/tty/prompt/converter_dsl.rb +19 -0
- data/lib/tty/prompt/converter_registry.rb +56 -0
- data/lib/tty/prompt/converters.rb +77 -0
- data/lib/tty/prompt/evaluator.rb +29 -0
- data/lib/tty/prompt/list.rb +38 -36
- data/lib/tty/prompt/mask_question.rb +85 -0
- data/lib/tty/prompt/multi_list.rb +21 -32
- data/lib/tty/prompt/question.rb +184 -162
- data/lib/tty/prompt/question/checks.rb +85 -0
- data/lib/tty/prompt/question/modifier.rb +4 -5
- data/lib/tty/prompt/question/validation.rb +29 -35
- data/lib/tty/prompt/reader.rb +98 -52
- data/lib/tty/prompt/reader/codes.rb +63 -0
- data/lib/tty/prompt/reader/key_event.rb +67 -0
- data/lib/tty/prompt/reader/mode.rb +66 -0
- data/lib/tty/prompt/reader/mode/echo.rb +43 -0
- data/lib/tty/prompt/reader/mode/raw.rb +43 -0
- data/lib/tty/prompt/result.rb +42 -0
- data/lib/tty/prompt/statement.rb +9 -14
- data/lib/tty/prompt/suggestion.rb +4 -2
- data/lib/tty/prompt/symbols.rb +13 -0
- data/lib/tty/prompt/test.rb +3 -2
- data/lib/tty/prompt/utils.rb +1 -1
- data/lib/tty/prompt/version.rb +1 -1
- data/spec/unit/ask_spec.rb +31 -48
- data/spec/unit/choice/eql_spec.rb +0 -2
- data/spec/unit/choice/from_spec.rb +0 -2
- data/spec/unit/choices/add_spec.rb +0 -2
- data/spec/unit/choices/each_spec.rb +0 -2
- data/spec/unit/choices/new_spec.rb +0 -2
- data/spec/unit/choices/pluck_spec.rb +0 -2
- data/spec/unit/converters/convert_bool_spec.rb +58 -0
- data/spec/unit/{response/read_char_spec.rb → converters/convert_char_spec.rb} +2 -4
- data/spec/unit/converters/convert_custom_spec.rb +14 -0
- data/spec/unit/converters/convert_date_spec.rb +25 -0
- data/spec/unit/converters/convert_file_spec.rb +14 -0
- data/spec/unit/{response/read_number_spec.rb → converters/convert_number_spec.rb} +5 -7
- data/spec/unit/converters/convert_path_spec.rb +15 -0
- data/spec/unit/{response/read_range_spec.rb → converters/convert_range_spec.rb} +3 -5
- data/spec/unit/converters/convert_regex_spec.rb +12 -0
- data/spec/unit/converters/convert_string_spec.rb +21 -0
- data/spec/unit/distance/distance_spec.rb +0 -2
- data/spec/unit/error_spec.rb +0 -6
- data/spec/unit/evaluator_spec.rb +67 -0
- data/spec/unit/keypress_spec.rb +19 -0
- data/spec/unit/mask_spec.rb +95 -0
- data/spec/unit/multi_select_spec.rb +36 -24
- data/spec/unit/multiline_spec.rb +19 -0
- data/spec/unit/new_spec.rb +18 -0
- data/spec/unit/ok_spec.rb +10 -0
- data/spec/unit/question/default_spec.rb +17 -4
- data/spec/unit/question/echo_spec.rb +31 -0
- data/spec/unit/question/in_spec.rb +48 -16
- data/spec/unit/question/initialize_spec.rb +2 -9
- data/spec/unit/question/modifier/apply_to_spec.rb +9 -16
- data/spec/unit/question/modifier/letter_case_spec.rb +0 -2
- data/spec/unit/question/modifier/whitespace_spec.rb +12 -20
- data/spec/unit/question/modify_spec.rb +3 -7
- data/spec/unit/question/required_spec.rb +20 -14
- data/spec/unit/question/validate_spec.rb +20 -19
- data/spec/unit/question/validation/call_spec.rb +15 -6
- data/spec/unit/question/validation/coerce_spec.rb +17 -11
- data/spec/unit/reader/publish_keypress_event_spec.rb +81 -0
- data/spec/unit/reader/read_keypress_spec.rb +22 -0
- data/spec/unit/reader/read_line_spec.rb +31 -0
- data/spec/unit/reader/read_multiline_spec.rb +37 -0
- data/spec/unit/result_spec.rb +40 -0
- data/spec/unit/say_spec.rb +18 -23
- data/spec/unit/select_spec.rb +37 -32
- data/spec/unit/statement/initialize_spec.rb +4 -4
- data/spec/unit/suggest_spec.rb +0 -2
- data/spec/unit/warn_spec.rb +0 -5
- data/spec/unit/yes_no_spec.rb +70 -0
- data/tty-prompt.gemspec +7 -4
- metadata +123 -40
- data/lib/tty/prompt/codes.rb +0 -32
- data/lib/tty/prompt/cursor.rb +0 -131
- data/lib/tty/prompt/error.rb +0 -26
- data/lib/tty/prompt/mode.rb +0 -64
- data/lib/tty/prompt/mode/echo.rb +0 -41
- data/lib/tty/prompt/mode/raw.rb +0 -41
- data/lib/tty/prompt/response.rb +0 -247
- data/lib/tty/prompt/response_delegation.rb +0 -42
- data/spec/unit/cursor/new_spec.rb +0 -74
- data/spec/unit/question/character_spec.rb +0 -13
- data/spec/unit/reader/getc_spec.rb +0 -42
- data/spec/unit/response/read_bool_spec.rb +0 -58
- data/spec/unit/response/read_date_spec.rb +0 -16
- data/spec/unit/response/read_email_spec.rb +0 -45
- data/spec/unit/response/read_multiple_spec.rb +0 -21
- data/spec/unit/response/read_spec.rb +0 -69
- data/spec/unit/response/read_string_spec.rb +0 -14
@@ -0,0 +1,67 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
require 'tty/prompt/reader/codes'
|
4
|
+
|
5
|
+
module TTY
|
6
|
+
class Prompt
|
7
|
+
class Reader
|
8
|
+
# Responsible for meta-data information about key pressed
|
9
|
+
#
|
10
|
+
# @api private
|
11
|
+
class Key < Struct.new(:name, :ctrl, :meta, :shift)
|
12
|
+
def initialize(*)
|
13
|
+
super
|
14
|
+
@ctrl = false
|
15
|
+
@meta = false
|
16
|
+
@shift = false
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
# Represents key event emitted during keyboard press
|
21
|
+
#
|
22
|
+
# @api public
|
23
|
+
class KeyEvent < Struct.new(:value, :key)
|
24
|
+
META_KEY_CODE_RE = /^(?:\x1b+)(O|N|\[|\[\[)(?:(\d+)(?:;(\d+))?([~^$])|(?:1;)?(\d+)?([a-zA-Z]))/
|
25
|
+
|
26
|
+
def self.from(char)
|
27
|
+
key = Key.new
|
28
|
+
case char
|
29
|
+
when Codes::RETURN
|
30
|
+
key.name = :return
|
31
|
+
when Codes::LINEFEED
|
32
|
+
key.name = :enter
|
33
|
+
when Codes::TAB
|
34
|
+
key.name = :tab
|
35
|
+
when Codes::BACKSPACE
|
36
|
+
key.name = :backspace
|
37
|
+
when Codes::DELETE
|
38
|
+
key.name = :delete
|
39
|
+
when Codes::SPACE
|
40
|
+
key.name = :space
|
41
|
+
when Codes::CTRL_C, Codes::ESCAPE
|
42
|
+
key.name = :escape
|
43
|
+
when proc { |c| c <= "\x1a" }
|
44
|
+
codes = char.each_codepoint.to_a
|
45
|
+
key.name = "#{codes}"
|
46
|
+
key.ctrl = true
|
47
|
+
when /\d/
|
48
|
+
key.name = :num
|
49
|
+
when META_KEY_CODE_RE
|
50
|
+
key.meta = true
|
51
|
+
case char
|
52
|
+
when Codes::KEY_UP, Codes::CTRL_K, Codes::CTRL_P
|
53
|
+
key.name = :up
|
54
|
+
when Codes::KEY_DOWN, Codes::CTRL_J, Codes::CTRL_N
|
55
|
+
key.name = :down
|
56
|
+
when Codes::KEY_RIGHT, Codes::CTRL_L
|
57
|
+
key.name = :right
|
58
|
+
when Codes::KEY_LEFT, Codes::CTRL_H
|
59
|
+
key.name = :left
|
60
|
+
end
|
61
|
+
end
|
62
|
+
new(char, key)
|
63
|
+
end
|
64
|
+
end # KeyEvent
|
65
|
+
end # Reader
|
66
|
+
end # Prompt
|
67
|
+
end # TTY
|
@@ -0,0 +1,66 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
require 'tty/prompt/reader/mode/echo'
|
4
|
+
require 'tty/prompt/reader/mode/raw'
|
5
|
+
|
6
|
+
module TTY
|
7
|
+
class Prompt
|
8
|
+
class Reader
|
9
|
+
class Mode
|
10
|
+
# Initialize a Terminal
|
11
|
+
#
|
12
|
+
# @api public
|
13
|
+
def initialize(options = {})
|
14
|
+
@echo = Echo.new
|
15
|
+
@raw = Raw.new
|
16
|
+
end
|
17
|
+
|
18
|
+
# Switch echo on
|
19
|
+
#
|
20
|
+
# @api public
|
21
|
+
def echo_on
|
22
|
+
@echo.on
|
23
|
+
end
|
24
|
+
|
25
|
+
# Switch echo off
|
26
|
+
#
|
27
|
+
# @api public
|
28
|
+
def echo_off
|
29
|
+
@echo.off
|
30
|
+
end
|
31
|
+
|
32
|
+
# Echo given block
|
33
|
+
#
|
34
|
+
# @param [Boolean] is_on
|
35
|
+
#
|
36
|
+
# @api public
|
37
|
+
def echo(is_on = true, &block)
|
38
|
+
@echo.echo(is_on, &block)
|
39
|
+
end
|
40
|
+
|
41
|
+
# Switch raw mode on
|
42
|
+
#
|
43
|
+
# @api public
|
44
|
+
def raw_on
|
45
|
+
@raw.on
|
46
|
+
end
|
47
|
+
|
48
|
+
# Switch raw mode off
|
49
|
+
#
|
50
|
+
# @api public
|
51
|
+
def raw_off
|
52
|
+
@raw.off
|
53
|
+
end
|
54
|
+
|
55
|
+
# Use raw mode in the given block
|
56
|
+
#
|
57
|
+
# @param [Boolean] is_on
|
58
|
+
#
|
59
|
+
# @api public
|
60
|
+
def raw(is_on = true, &block)
|
61
|
+
@raw.raw(is_on, &block)
|
62
|
+
end
|
63
|
+
end # Mode
|
64
|
+
end # Reader
|
65
|
+
end # Prompt
|
66
|
+
end # TTY
|
@@ -0,0 +1,43 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
module TTY
|
4
|
+
class Prompt
|
5
|
+
class Reader
|
6
|
+
class Mode
|
7
|
+
# A class responsible for toggling echo.
|
8
|
+
class Echo
|
9
|
+
# Turn echo on
|
10
|
+
#
|
11
|
+
# @api public
|
12
|
+
def on
|
13
|
+
%x{stty echo} if TTY::Platform.unix?
|
14
|
+
end
|
15
|
+
|
16
|
+
# Turn echo off
|
17
|
+
#
|
18
|
+
# @api public
|
19
|
+
def off
|
20
|
+
%x{stty -echo} if TTY::Platform.unix?
|
21
|
+
end
|
22
|
+
|
23
|
+
# Wrap code block inside echo
|
24
|
+
#
|
25
|
+
# @api public
|
26
|
+
def echo(is_on=true, &block)
|
27
|
+
value = nil
|
28
|
+
off unless is_on
|
29
|
+
value = block.call if block_given?
|
30
|
+
rescue NoMethodError, Interrupt => error
|
31
|
+
puts "#{error.class} #{error.message}"
|
32
|
+
puts error.backtrace
|
33
|
+
on
|
34
|
+
exit
|
35
|
+
ensure
|
36
|
+
on
|
37
|
+
value
|
38
|
+
end
|
39
|
+
end # Echo
|
40
|
+
end # Mode
|
41
|
+
end # Reader
|
42
|
+
end # Prompt
|
43
|
+
end # TTY
|
@@ -0,0 +1,43 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
module TTY
|
4
|
+
class Prompt
|
5
|
+
class Reader
|
6
|
+
class Mode
|
7
|
+
# A class responsible for toggling raw mode.
|
8
|
+
class Raw
|
9
|
+
# Turn raw mode on
|
10
|
+
#
|
11
|
+
# @api public
|
12
|
+
def on
|
13
|
+
%x{stty raw} if TTY::Platform.unix?
|
14
|
+
end
|
15
|
+
|
16
|
+
# Turn raw mode off
|
17
|
+
#
|
18
|
+
# @api public
|
19
|
+
def off
|
20
|
+
%x{stty -raw} if TTY::Platform.unix?
|
21
|
+
end
|
22
|
+
|
23
|
+
# Wrap code block inside raw mode
|
24
|
+
#
|
25
|
+
# @api public
|
26
|
+
def raw(is_on=true, &block)
|
27
|
+
value = nil
|
28
|
+
on if is_on
|
29
|
+
value = block.call if block_given?
|
30
|
+
rescue NoMethodError, Interrupt => error
|
31
|
+
puts "#{error.class} #{error.message}"
|
32
|
+
puts error.backtrace
|
33
|
+
off
|
34
|
+
exit
|
35
|
+
ensure
|
36
|
+
off
|
37
|
+
value
|
38
|
+
end
|
39
|
+
end # Raw
|
40
|
+
end # Mode
|
41
|
+
end # Reader
|
42
|
+
end # Prompt
|
43
|
+
end # TTY
|
@@ -0,0 +1,42 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
module TTY
|
4
|
+
class Prompt
|
5
|
+
# Accumulates errors
|
6
|
+
class Result
|
7
|
+
attr_reader :question, :value, :errors
|
8
|
+
|
9
|
+
def initialize(question, value, errors = [])
|
10
|
+
@question = question
|
11
|
+
@value = value
|
12
|
+
@errors = errors
|
13
|
+
end
|
14
|
+
|
15
|
+
def with(condition = nil, &block)
|
16
|
+
validator = (condition || block)
|
17
|
+
(new_value, validation_error) = validator.call(question, value)
|
18
|
+
accumulated_errors = errors + Array(validation_error)
|
19
|
+
|
20
|
+
if accumulated_errors.empty?
|
21
|
+
Success.new(question, new_value)
|
22
|
+
else
|
23
|
+
Failure.new(question, new_value, accumulated_errors)
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
def success?
|
28
|
+
is_a?(Success)
|
29
|
+
end
|
30
|
+
|
31
|
+
def failure?
|
32
|
+
is_a?(Failure)
|
33
|
+
end
|
34
|
+
|
35
|
+
class Success < Result
|
36
|
+
end
|
37
|
+
|
38
|
+
class Failure < Result
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end # Prompt
|
42
|
+
end # TTY
|
data/lib/tty/prompt/statement.rb
CHANGED
@@ -5,10 +5,6 @@ module TTY
|
|
5
5
|
class Prompt
|
6
6
|
# A class representing a statement output to prompt.
|
7
7
|
class Statement
|
8
|
-
# @api private
|
9
|
-
attr_reader :prompt
|
10
|
-
private :prompt
|
11
|
-
|
12
8
|
# Flag to display newline
|
13
9
|
#
|
14
10
|
# @api public
|
@@ -32,11 +28,10 @@ module TTY
|
|
32
28
|
# change the message display to color
|
33
29
|
#
|
34
30
|
# @api public
|
35
|
-
def initialize(prompt
|
36
|
-
@prompt
|
37
|
-
@
|
38
|
-
@
|
39
|
-
@color = options.fetch(:color, false)
|
31
|
+
def initialize(prompt, options = {})
|
32
|
+
@prompt = prompt
|
33
|
+
@newline = options.fetch(:newline) { true }
|
34
|
+
@color = options.fetch(:color) { false }
|
40
35
|
end
|
41
36
|
|
42
37
|
# Output the message to the prompt
|
@@ -45,14 +40,14 @@ module TTY
|
|
45
40
|
# the message to be printed to stdout
|
46
41
|
#
|
47
42
|
# @api public
|
48
|
-
def
|
49
|
-
message = @
|
43
|
+
def call(message)
|
44
|
+
message = @prompt.decorate(message, *color) if color
|
50
45
|
|
51
46
|
if newline && /( |\t)(\e\[\d+(;\d+)*m)?\Z/ !~ message
|
52
|
-
prompt.
|
47
|
+
@prompt.puts message
|
53
48
|
else
|
54
|
-
prompt.
|
55
|
-
prompt.
|
49
|
+
@prompt.print message
|
50
|
+
@prompt.flush
|
56
51
|
end
|
57
52
|
end
|
58
53
|
end # Statement
|
@@ -3,9 +3,11 @@
|
|
3
3
|
require 'tty/prompt/distance'
|
4
4
|
|
5
5
|
module TTY
|
6
|
-
# A class responsible for
|
6
|
+
# A class responsible for terminal prompt interactions.
|
7
7
|
class Prompt
|
8
|
-
# A class representing a suggestion
|
8
|
+
# A class representing a suggestion out of possible choices
|
9
|
+
#
|
10
|
+
# @api public
|
9
11
|
class Suggestion
|
10
12
|
DEFAULT_INDENT = 8
|
11
13
|
|
data/lib/tty/prompt/test.rb
CHANGED
@@ -8,8 +8,9 @@ module TTY
|
|
8
8
|
def initialize(options = {})
|
9
9
|
@input = StringIO.new
|
10
10
|
@output = StringIO.new
|
11
|
-
|
12
|
-
super(
|
11
|
+
options.merge!({input: @input, output: @output})
|
12
|
+
super(options)
|
13
|
+
@pastel = Pastel.new(enabled: true)
|
13
14
|
end
|
14
15
|
end # TestPrompt
|
15
16
|
end # TTY
|
data/lib/tty/prompt/utils.rb
CHANGED
data/lib/tty/prompt/version.rb
CHANGED
data/spec/unit/ask_spec.rb
CHANGED
@@ -1,74 +1,57 @@
|
|
1
1
|
# encoding: utf-8
|
2
2
|
|
3
|
-
require 'spec_helper'
|
4
|
-
|
5
3
|
RSpec.describe TTY::Prompt, '#ask' do
|
6
|
-
|
7
4
|
subject(:prompt) { TTY::TestPrompt.new }
|
8
5
|
|
9
|
-
it '
|
10
|
-
prompt.ask(
|
11
|
-
expect(prompt.output.string).to
|
6
|
+
it 'asks question' do
|
7
|
+
prompt.ask('What is your name?')
|
8
|
+
expect(prompt.output.string).to eq([
|
9
|
+
"What is your name? ",
|
10
|
+
"\e[1000D\e[K\e[1A",
|
11
|
+
"\e[1000D\e[K",
|
12
|
+
"What is your name? \n"
|
13
|
+
].join)
|
12
14
|
end
|
13
15
|
|
14
|
-
it '
|
16
|
+
it 'asks an empty question ' do
|
15
17
|
prompt.ask('')
|
16
18
|
expect(prompt.output.string).to eql('')
|
17
19
|
end
|
18
20
|
|
19
|
-
it '
|
21
|
+
it 'asks an empty question and returns nil if EOF is sent to stdin' do
|
20
22
|
prompt.input << nil
|
21
23
|
prompt.input.rewind
|
22
|
-
|
23
|
-
expect(
|
24
|
+
answer = prompt.ask('')
|
25
|
+
expect(answer).to eql(nil)
|
24
26
|
expect(prompt.output.string).to eq('')
|
25
27
|
end
|
26
28
|
|
27
|
-
it "asks a question with a prefix
|
28
|
-
prompt = TTY::TestPrompt.new(prefix:
|
29
|
-
prompt.input <<
|
29
|
+
it "asks a question with a prefix [?]" do
|
30
|
+
prompt = TTY::TestPrompt.new(prefix: "[?] ")
|
31
|
+
prompt.input << "\r"
|
30
32
|
prompt.input.rewind
|
31
|
-
|
32
|
-
expect(
|
33
|
-
expect(prompt.output.string).to
|
33
|
+
answer = prompt.ask 'Are you Polish?'
|
34
|
+
expect(answer).to eq(nil)
|
35
|
+
expect(prompt.output.string).to eq([
|
36
|
+
"[?] Are you Polish? ",
|
37
|
+
"\e[1000D\e[K\e[1A",
|
38
|
+
"\e[1000D\e[K",
|
39
|
+
"[?] Are you Polish? \n"
|
40
|
+
].join)
|
34
41
|
end
|
35
42
|
|
36
43
|
it 'asks a question with block' do
|
37
44
|
prompt.input << ''
|
38
45
|
prompt.input.rewind
|
39
|
-
|
46
|
+
answer = prompt.ask "What is your name?" do |q|
|
40
47
|
q.default 'Piotr'
|
41
48
|
end
|
42
|
-
expect(
|
43
|
-
expect(prompt.output.string).to eq(
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
prompt.input.rewind
|
50
|
-
expect(prompt.yes?("Are you a human?")).to eq(true)
|
51
|
-
expect(prompt.output.string).to eq('Are you a human?')
|
52
|
-
end
|
53
|
-
|
54
|
-
it 'disagrees' do
|
55
|
-
prompt.input << 'no'
|
56
|
-
prompt.input.rewind
|
57
|
-
expect(prompt.yes?("Are you a human?")).to eq(false)
|
58
|
-
end
|
59
|
-
end
|
60
|
-
|
61
|
-
context 'no?' do
|
62
|
-
it 'agrees' do
|
63
|
-
prompt.input << 'no'
|
64
|
-
prompt.input.rewind
|
65
|
-
expect(prompt.no?("Are you a human?")).to eq(true)
|
66
|
-
end
|
67
|
-
|
68
|
-
it 'disagrees' do
|
69
|
-
prompt.input << 'yes'
|
70
|
-
prompt.input.rewind
|
71
|
-
expect(prompt.no?("Are you a human?")).to eq(false)
|
72
|
-
end
|
49
|
+
expect(answer).to eq('Piotr')
|
50
|
+
expect(prompt.output.string).to eq([
|
51
|
+
"What is your name? \e[90m(Piotr)\e[0m ",
|
52
|
+
"\e[1000D\e[K\e[1A",
|
53
|
+
"\e[1000D\e[K",
|
54
|
+
"What is your name? \e[32mPiotr\e[0m\n"
|
55
|
+
].join)
|
73
56
|
end
|
74
57
|
end
|