tty-prompt 0.10.1 → 0.11.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.travis.yml +0 -1
- data/CHANGELOG.md +30 -0
- data/README.md +39 -9
- data/examples/echo.rb +5 -1
- data/examples/inputs.rb +10 -0
- data/examples/mask.rb +6 -2
- data/examples/multi_select.rb +1 -1
- data/examples/multi_select_paged.rb +9 -0
- data/examples/select.rb +5 -5
- data/examples/slider.rb +1 -1
- data/lib/tty-prompt.rb +2 -36
- data/lib/tty/prompt.rb +49 -8
- data/lib/tty/prompt/choices.rb +2 -0
- data/lib/tty/prompt/confirm_question.rb +6 -1
- data/lib/tty/prompt/converter_dsl.rb +9 -6
- data/lib/tty/prompt/converter_registry.rb +27 -19
- data/lib/tty/prompt/converters.rb +16 -22
- data/lib/tty/prompt/enum_list.rb +8 -4
- data/lib/tty/prompt/enum_paginator.rb +2 -0
- data/lib/tty/prompt/evaluator.rb +1 -1
- data/lib/tty/prompt/expander.rb +1 -1
- data/lib/tty/prompt/list.rb +21 -11
- data/lib/tty/prompt/mask_question.rb +15 -6
- data/lib/tty/prompt/multi_list.rb +12 -10
- data/lib/tty/prompt/question.rb +38 -36
- data/lib/tty/prompt/question/modifier.rb +2 -0
- data/lib/tty/prompt/question/validation.rb +5 -4
- data/lib/tty/prompt/reader.rb +104 -58
- data/lib/tty/prompt/reader/codes.rb +103 -63
- data/lib/tty/prompt/reader/console.rb +57 -0
- data/lib/tty/prompt/reader/key_event.rb +51 -88
- data/lib/tty/prompt/reader/mode.rb +5 -5
- data/lib/tty/prompt/reader/win_api.rb +29 -0
- data/lib/tty/prompt/reader/win_console.rb +49 -0
- data/lib/tty/prompt/slider.rb +10 -6
- data/lib/tty/prompt/suggestion.rb +1 -1
- data/lib/tty/prompt/symbols.rb +52 -10
- data/lib/tty/prompt/version.rb +1 -1
- data/lib/tty/{prompt/test.rb → test_prompt.rb} +2 -1
- data/spec/unit/ask_spec.rb +8 -16
- data/spec/unit/converters/convert_bool_spec.rb +1 -2
- data/spec/unit/converters/on_error_spec.rb +9 -0
- data/spec/unit/enum_paginator_spec.rb +16 -0
- data/spec/unit/enum_select_spec.rb +69 -25
- data/spec/unit/expand_spec.rb +14 -14
- data/spec/unit/mask_spec.rb +66 -29
- data/spec/unit/multi_select_spec.rb +120 -74
- data/spec/unit/new_spec.rb +5 -3
- data/spec/unit/paginator_spec.rb +16 -0
- data/spec/unit/question/default_spec.rb +2 -4
- data/spec/unit/question/echo_spec.rb +2 -3
- data/spec/unit/question/in_spec.rb +9 -14
- data/spec/unit/question/modifier/letter_case_spec.rb +32 -11
- data/spec/unit/question/modifier/whitespace_spec.rb +41 -15
- data/spec/unit/question/required_spec.rb +9 -13
- data/spec/unit/question/validate_spec.rb +7 -10
- data/spec/unit/reader/key_event_spec.rb +36 -50
- data/spec/unit/reader/publish_keypress_event_spec.rb +5 -3
- data/spec/unit/reader/read_keypress_spec.rb +8 -7
- data/spec/unit/reader/read_line_spec.rb +9 -9
- data/spec/unit/reader/read_multiline_spec.rb +8 -7
- data/spec/unit/select_spec.rb +85 -25
- data/spec/unit/slider_spec.rb +43 -16
- data/spec/unit/yes_no_spec.rb +14 -28
- data/tasks/console.rake +1 -0
- data/tty-prompt.gemspec +2 -2
- metadata +14 -7
@@ -0,0 +1,57 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
require_relative 'codes'
|
4
|
+
require_relative 'mode'
|
5
|
+
|
6
|
+
module TTY
|
7
|
+
class Prompt
|
8
|
+
class Reader
|
9
|
+
class Console
|
10
|
+
ESC = "\e".freeze
|
11
|
+
CSI = "\e[".freeze
|
12
|
+
|
13
|
+
# Key codes
|
14
|
+
#
|
15
|
+
# @return [Hash[Symbol]]
|
16
|
+
#
|
17
|
+
# @api public
|
18
|
+
attr_reader :keys
|
19
|
+
|
20
|
+
# Escape codes
|
21
|
+
#
|
22
|
+
# @return [Array[Integer]]
|
23
|
+
#
|
24
|
+
# @api public
|
25
|
+
attr_reader :escape_codes
|
26
|
+
|
27
|
+
def initialize(input)
|
28
|
+
@input = input
|
29
|
+
@mode = Mode.new
|
30
|
+
@keys = Codes.keys
|
31
|
+
@escape_codes = [[ESC.ord], CSI.bytes.to_a]
|
32
|
+
end
|
33
|
+
|
34
|
+
# Get a character from console with echo
|
35
|
+
#
|
36
|
+
# @param [Hash[Symbol]] options
|
37
|
+
# @option options [Symbol] :echo
|
38
|
+
# the echo toggle
|
39
|
+
#
|
40
|
+
# @return [String]
|
41
|
+
#
|
42
|
+
# @api private
|
43
|
+
def get_char(options)
|
44
|
+
mode.raw(options[:raw]) do
|
45
|
+
mode.echo(options[:echo]) { input.getc }
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
protected
|
50
|
+
|
51
|
+
attr_reader :mode
|
52
|
+
|
53
|
+
attr_reader :input
|
54
|
+
end # Console
|
55
|
+
end # Reader
|
56
|
+
end # Prompt
|
57
|
+
end # TTY
|
@@ -1,7 +1,5 @@
|
|
1
1
|
# encoding: utf-8
|
2
2
|
|
3
|
-
require 'tty/prompt/reader/codes'
|
4
|
-
|
5
3
|
module TTY
|
6
4
|
class Prompt
|
7
5
|
class Reader
|
@@ -10,10 +8,7 @@ module TTY
|
|
10
8
|
# @api private
|
11
9
|
class Key < Struct.new(:name, :ctrl, :meta, :shift)
|
12
10
|
def initialize(*)
|
13
|
-
super
|
14
|
-
@ctrl = false
|
15
|
-
@meta = false
|
16
|
-
@shift = false
|
11
|
+
super(nil, false, false, false)
|
17
12
|
end
|
18
13
|
end
|
19
14
|
|
@@ -21,101 +16,69 @@ module TTY
|
|
21
16
|
#
|
22
17
|
# @api public
|
23
18
|
class KeyEvent < Struct.new(:value, :key)
|
24
|
-
|
25
|
-
|
26
|
-
|
19
|
+
# Create key event from read input codes
|
20
|
+
#
|
21
|
+
# @param [Hash[Symbol]] keys
|
22
|
+
# the keys and codes mapping
|
23
|
+
# @param [Array[Integer]] codes
|
24
|
+
#
|
25
|
+
# @return [KeyEvent]
|
26
|
+
#
|
27
|
+
# @api public
|
28
|
+
def self.from(keys, char)
|
27
29
|
key = Key.new
|
30
|
+
ctrls = keys.keys.grep(/ctrl/)
|
28
31
|
|
29
32
|
case char
|
30
|
-
when
|
31
|
-
when
|
32
|
-
when
|
33
|
-
when
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
when
|
40
|
-
key.name = :
|
41
|
-
key.meta = (char.size == 2)
|
42
|
-
when Codes::ESCAPE, "#{Codes::ESCAPE}#{Codes::ESCAPE}"
|
43
|
-
key.name = :escape
|
44
|
-
key.meta = (char.size == 2)
|
45
|
-
when proc { |c| c.length == 1 && c =~ /[a-z]/ }
|
46
|
-
key.name = char
|
47
|
-
when proc { |c| c.length == 1 && c =~ /[A-Z]/ }
|
48
|
-
key.name = char.downcase
|
33
|
+
when keys[:return] then key.name = :return
|
34
|
+
when keys[:enter] then key.name = :enter
|
35
|
+
when keys[:tab] then key.name = :tab
|
36
|
+
when keys[:backspace] then key.name = :backspace
|
37
|
+
when keys[:delete] then key.name = :delete
|
38
|
+
when keys[:space] then key.name = :space
|
39
|
+
when keys[:escape] then key.name = :escape
|
40
|
+
when proc { |c| c =~ /^[a-z]{1}$/ }
|
41
|
+
key.name = :alpha
|
42
|
+
when proc { |c| c =~ /^[A-Z]{1}$/ }
|
43
|
+
key.name = :alpha
|
49
44
|
key.shift = true
|
50
|
-
when /^\d+$/
|
45
|
+
when proc { |c| c =~ /^\d+$/ }
|
51
46
|
key.name = :num
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
when Codes::KEY_UP, Codes::KEY_UP_XTERM,
|
77
|
-
Codes::CTRL_K, Codes::CTRL_P
|
78
|
-
key.name = :up
|
79
|
-
when Codes::KEY_UP_SHIFT then key.name = :up; key.shift = true
|
80
|
-
when Codes::KEY_UP_CTRL then key.name = :up; key.ctrl = true
|
81
|
-
|
82
|
-
when Codes::KEY_DOWN, Codes::KEY_DOWN_XTERM,
|
83
|
-
Codes::CTRL_J, Codes::CTRL_N
|
84
|
-
key.name = :down
|
85
|
-
when Codes::KEY_DOWN_SHIFT then key.name = :down; key.shift = true
|
86
|
-
when Codes::KEY_DOWN_CTRL then key.name = :down; key.ctrl = true
|
87
|
-
|
88
|
-
when Codes::KEY_RIGHT, Codes::KEY_RIGHT_XTERM, Codes::CTRL_L
|
89
|
-
key.name = :right
|
90
|
-
when Codes::KEY_RIGHT_SHIFT then key.name = :right; key.shift = true
|
91
|
-
when Codes::KEY_RIGHT_CTRL then key.name = :right; key.ctrl = true
|
92
|
-
|
93
|
-
when Codes::KEY_LEFT, Codes::KEY_LEFT_XTERM, Codes::CTRL_H
|
94
|
-
key.name = :left
|
95
|
-
when Codes::KEY_LEFT_SHIFT then key.name = :left; key.shift = true
|
96
|
-
when Codes::KEY_LEFT_CTRL then key.name = :left; key.ctrl = true
|
97
|
-
|
98
|
-
when Codes::KEY_CLEAR, Codes::KEY_CLEAR_XTERM
|
99
|
-
key.name = :clear
|
100
|
-
when Codes::KEY_CLEAR_SHIFT then key.name = :clear; key.shift = true
|
101
|
-
when Codes::KEY_CLEAR_CTRL then key.name = :clear; key.ctrl = true
|
102
|
-
|
103
|
-
when Codes::KEY_END, Codes::KEY_END_XTERM
|
104
|
-
key.name = :end
|
105
|
-
|
106
|
-
when Codes::KEY_HOME, Codes::KEY_HOME_XTERM
|
107
|
-
key.name = :home
|
108
|
-
end
|
47
|
+
# arrows
|
48
|
+
when keys[:up] then key.name = :up
|
49
|
+
when keys[:down] then key.name = :down
|
50
|
+
when keys[:left] then key.name = :left
|
51
|
+
when keys[:right] then key.name = :right
|
52
|
+
when keys[:clear] then key.name = :clear
|
53
|
+
when keys[:end] then key.name = :end
|
54
|
+
when keys[:home] then key.name = :home
|
55
|
+
when proc { |cs| ctrls.any? { |name| keys[name] == cs } }
|
56
|
+
key.name = keys.key(char)
|
57
|
+
key.ctrl = true
|
58
|
+
# f1 - f12
|
59
|
+
when keys[:f1], keys[:f1_xterm] then key.name = :f1
|
60
|
+
when keys[:f2], keys[:f2_xterm] then key.name = :f2
|
61
|
+
when keys[:f3], keys[:f3_xterm] then key.name = :f3
|
62
|
+
when keys[:f4], keys[:f4_xterm] then key.name = :f4
|
63
|
+
when keys[:f5] then key.name = :f5
|
64
|
+
when keys[:f6] then key.name = :f6
|
65
|
+
when keys[:f7] then key.name = :f7
|
66
|
+
when keys[:f8] then key.name = :f8
|
67
|
+
when keys[:f9] then key.name = :f9
|
68
|
+
when keys[:f10] then key.name = :f10
|
69
|
+
when keys[:f11] then key.name = :f11
|
70
|
+
when keys[:f12] then key.name = :f12
|
109
71
|
end
|
72
|
+
|
110
73
|
new(char, key)
|
111
74
|
end
|
112
75
|
|
113
|
-
# Check if key event can be
|
76
|
+
# Check if key event can be triggered
|
114
77
|
#
|
115
78
|
# @return [Boolean]
|
116
79
|
#
|
117
80
|
# @api public
|
118
|
-
def
|
81
|
+
def trigger?
|
119
82
|
!key.nil? && !key.name.nil?
|
120
83
|
end
|
121
84
|
end # KeyEvent
|
@@ -19,11 +19,11 @@ module TTY
|
|
19
19
|
#
|
20
20
|
# @api public
|
21
21
|
def echo(is_on = true, &block)
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
22
|
+
if is_on
|
23
|
+
yield
|
24
|
+
else
|
25
|
+
@input.noecho(&block)
|
26
|
+
end
|
27
27
|
end
|
28
28
|
|
29
29
|
# Use raw mode in the given block
|
@@ -0,0 +1,29 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
require 'fiddle'
|
4
|
+
|
5
|
+
module TTY
|
6
|
+
class Prompt
|
7
|
+
class Reader
|
8
|
+
module WinAPI
|
9
|
+
include Fiddle
|
10
|
+
|
11
|
+
Handle = RUBY_VERSION >= "2.0.0" ? Fiddle::Handle : DL::Handle
|
12
|
+
|
13
|
+
CRT_HANDLE = Handle.new("msvcrt") rescue Handle.new("crtdll")
|
14
|
+
|
15
|
+
def getch
|
16
|
+
@@getch ||= Fiddle::Function.new(CRT_HANDLE["_getch"], [], TYPE_INT)
|
17
|
+
@@getch.call
|
18
|
+
end
|
19
|
+
module_function :getch
|
20
|
+
|
21
|
+
def getche
|
22
|
+
@@getche ||= Fiddle::Function.new(CRT_HANDLE["_getche"], [], TYPE_INT)
|
23
|
+
@@getche.call
|
24
|
+
end
|
25
|
+
module_function :getche
|
26
|
+
end # WinAPI
|
27
|
+
end # Reader
|
28
|
+
end # Prompt
|
29
|
+
end # TTY
|
@@ -0,0 +1,49 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
require_relative 'codes'
|
4
|
+
|
5
|
+
module TTY
|
6
|
+
class Prompt
|
7
|
+
class Reader
|
8
|
+
class WinConsole
|
9
|
+
ESC = "\e".freeze
|
10
|
+
NUL_HEX = "\x00".freeze
|
11
|
+
EXT_HEX = "\xE0".freeze
|
12
|
+
|
13
|
+
# Key codes
|
14
|
+
#
|
15
|
+
# @return [Hash[Symbol]]
|
16
|
+
#
|
17
|
+
# @api public
|
18
|
+
attr_reader :keys
|
19
|
+
|
20
|
+
# Escape codes
|
21
|
+
#
|
22
|
+
# @return [Array[Integer]]
|
23
|
+
#
|
24
|
+
# @api public
|
25
|
+
attr_reader :escape_codes
|
26
|
+
|
27
|
+
def initialize(input)
|
28
|
+
require_relative 'win_api'
|
29
|
+
@input = input
|
30
|
+
@keys = Codes.win_keys
|
31
|
+
@escape_codes = [[NUL_HEX.ord], [ESC.ord], EXT_HEX.bytes.to_a]
|
32
|
+
end
|
33
|
+
|
34
|
+
# Get a character from console with echo
|
35
|
+
#
|
36
|
+
# @param [Hash[Symbol]] options
|
37
|
+
# @option options [Symbol] :echo
|
38
|
+
# the echo toggle
|
39
|
+
#
|
40
|
+
# @return [String]
|
41
|
+
#
|
42
|
+
# @api private
|
43
|
+
def get_char(options)
|
44
|
+
options[:echo] ? @input.getc : WinAPI.getch.chr
|
45
|
+
end
|
46
|
+
end # Console
|
47
|
+
end # Reader
|
48
|
+
end # Prompt
|
49
|
+
end # TTY
|
data/lib/tty/prompt/slider.rb
CHANGED
@@ -1,5 +1,7 @@
|
|
1
1
|
# encoding: utf-8
|
2
2
|
|
3
|
+
require_relative 'symbols'
|
4
|
+
|
3
5
|
module TTY
|
4
6
|
# A class responsible for shell prompt interactions.
|
5
7
|
class Prompt
|
@@ -7,6 +9,8 @@ module TTY
|
|
7
9
|
#
|
8
10
|
# @api public
|
9
11
|
class Slider
|
12
|
+
include Symbols
|
13
|
+
|
10
14
|
HELP = '(Use arrow keys, press Enter to select)'.freeze
|
11
15
|
|
12
16
|
# Initailize a Slider
|
@@ -88,7 +92,7 @@ module TTY
|
|
88
92
|
alias_method :keydown, :keyleft
|
89
93
|
|
90
94
|
def keyright(*)
|
91
|
-
@active += 1 if (@active + @step)
|
95
|
+
@active += 1 if (@active + @step) < range.size
|
92
96
|
end
|
93
97
|
alias_method :keyup, :keyright
|
94
98
|
|
@@ -159,11 +163,11 @@ module TTY
|
|
159
163
|
# @api private
|
160
164
|
def render_slider
|
161
165
|
output = ''
|
162
|
-
output <<
|
163
|
-
output <<
|
164
|
-
output << @prompt.decorate(
|
165
|
-
output <<
|
166
|
-
output <<
|
166
|
+
output << symbols[:pipe]
|
167
|
+
output << symbols[:line] * @active
|
168
|
+
output << @prompt.decorate(symbols[:handle], @active_color)
|
169
|
+
output << symbols[:line] * (range.size - @active - 1)
|
170
|
+
output << symbols[:pipe]
|
167
171
|
output << " #{range[@active]}"
|
168
172
|
output
|
169
173
|
end
|
data/lib/tty/prompt/symbols.rb
CHANGED
@@ -2,18 +2,60 @@
|
|
2
2
|
|
3
3
|
module TTY
|
4
4
|
class Prompt
|
5
|
+
# Cross platform common Unicode symbols.
|
6
|
+
#
|
7
|
+
# @api public
|
5
8
|
module Symbols
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
+
KEYS = {
|
10
|
+
tick: '✓',
|
11
|
+
cross: '✘',
|
12
|
+
star: '★',
|
13
|
+
dot: '•',
|
14
|
+
pointer: '‣',
|
15
|
+
line: '─',
|
16
|
+
pipe: '|',
|
17
|
+
handle: 'O',
|
18
|
+
ellipsis: '…',
|
19
|
+
radio_on: '⬢',
|
20
|
+
radio_off: '⬡',
|
21
|
+
checkbox_on: '☒',
|
22
|
+
checkbox_off: '☐',
|
23
|
+
circle_on: 'ⓧ',
|
24
|
+
circle_off: 'Ⓘ'
|
25
|
+
}.freeze
|
9
26
|
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
27
|
+
WIN_KEYS = {
|
28
|
+
tick: '√',
|
29
|
+
cross: '×',
|
30
|
+
star: '*',
|
31
|
+
dot: '.',
|
32
|
+
pointer: '>',
|
33
|
+
line: '-',
|
34
|
+
pipe: '|',
|
35
|
+
handle: 'O',
|
36
|
+
ellipsis: '...',
|
37
|
+
radio_on: '(*)',
|
38
|
+
radio_off: '( )',
|
39
|
+
checkbox_on: '[×]',
|
40
|
+
checkbox_off: '[ ]',
|
41
|
+
circle_on: '(x)',
|
42
|
+
circle_off: '( )'
|
43
|
+
}.freeze
|
44
|
+
|
45
|
+
def symbols
|
46
|
+
@symbols ||= windows? ? WIN_KEYS : KEYS
|
47
|
+
end
|
48
|
+
module_function :symbols
|
49
|
+
|
50
|
+
# Check if Windowz
|
51
|
+
#
|
52
|
+
# @return [Boolean]
|
53
|
+
#
|
54
|
+
# @api public
|
55
|
+
def windows?
|
56
|
+
::File::ALT_SEPARATOR == "\\"
|
57
|
+
end
|
58
|
+
module_function :windows?
|
17
59
|
end # Symbols
|
18
60
|
end # Prompt
|
19
61
|
end # TTY
|