tty-prompt 0.7.1 → 0.8.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 +5 -4
- data/CHANGELOG.md +17 -0
- data/Gemfile +1 -0
- data/README.md +64 -3
- data/examples/ask.rb +8 -0
- data/examples/keypress.rb +11 -0
- data/examples/select.rb +10 -0
- data/lib/tty/prompt.rb +3 -3
- data/lib/tty/prompt/question/checks.rb +1 -1
- data/lib/tty/prompt/reader.rb +6 -3
- data/lib/tty/prompt/reader/codes.rb +50 -38
- data/lib/tty/prompt/reader/key_event.rb +43 -13
- data/lib/tty/prompt/version.rb +1 -1
- data/spec/unit/question/in_spec.rb +49 -0
- data/spec/unit/question/required_spec.rb +32 -2
- data/spec/unit/question/validate_spec.rb +49 -0
- data/spec/unit/reader/key_event_spec.rb +69 -40
- metadata +3 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: f9a360c4b3a7cd670868b20f7b220223274f555b
|
4
|
+
data.tar.gz: 17667527540e13b4e91eca3c042c9fcc5cb24b86
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: e7489d7dcde47bad702c66740391db047b17d94eedb9c110de0b77ffc59ba6c4cca83f95d2e5dc85417b6e1e5f409d0bd88c5a00367e260b29ea48f3efe7c798
|
7
|
+
data.tar.gz: ef5bec91d62387297bbc29b947bb39d014e3a2239d5679a9175e6944520d490caca7f327abf695c3b27f42055169c89cd7b924522dec1a55e61e94ed2e9e379b
|
data/.travis.yml
CHANGED
@@ -6,10 +6,10 @@ bundler_args: --without tools
|
|
6
6
|
script: "bundle exec rake ci"
|
7
7
|
rvm:
|
8
8
|
- 1.9.3
|
9
|
-
- 2.0
|
10
|
-
- 2.1
|
11
|
-
- 2.2
|
12
|
-
- 2.3.
|
9
|
+
- 2.0.0
|
10
|
+
- 2.1.10
|
11
|
+
- 2.2.5
|
12
|
+
- 2.3.1
|
13
13
|
- ruby-head
|
14
14
|
- jruby-9000
|
15
15
|
- jruby-head
|
@@ -18,6 +18,7 @@ matrix:
|
|
18
18
|
allow_failures:
|
19
19
|
- rvm: ruby-head
|
20
20
|
- rvm: jruby-head
|
21
|
+
- rvm: rbx-2
|
21
22
|
fast_finish: true
|
22
23
|
branches:
|
23
24
|
only: master
|
data/CHANGELOG.md
CHANGED
@@ -1,5 +1,21 @@
|
|
1
1
|
# Change log
|
2
2
|
|
3
|
+
## [v0.8.0] - 2016-11-29
|
4
|
+
|
5
|
+
### Added
|
6
|
+
* Add ability to publish custom key events for VIM keybindings customisations etc...
|
7
|
+
|
8
|
+
### Fixed
|
9
|
+
* Fix Reader#read_char to use Ruby internal buffers instead of direct system call by @kke(Kimmo Lehto)
|
10
|
+
* Fix issue with #ask required & validate checks to take into account required when validating values
|
11
|
+
* Fix bug with #read_keypress to handle function keys and meta navigation keys
|
12
|
+
* Fix issue with default messages not displaying for `range`, `required` and `validate`
|
13
|
+
|
14
|
+
## [v0.7.1] - 2016-08-07
|
15
|
+
|
16
|
+
### Fixed
|
17
|
+
* Fix Reader::Mode to include standard io library
|
18
|
+
|
3
19
|
## [v0.7.0] - 2016-07-17
|
4
20
|
|
5
21
|
### Added
|
@@ -98,6 +114,7 @@
|
|
98
114
|
|
99
115
|
* Initial implementation and release
|
100
116
|
|
117
|
+
[v0.7.1]: https://github.com/piotrmurach/tty-prompt/compare/v0.7.0...v0.7.1
|
101
118
|
[v0.7.0]: https://github.com/piotrmurach/tty-prompt/compare/v0.6.0...v0.7.0
|
102
119
|
[v0.6.0]: https://github.com/piotrmurach/tty-prompt/compare/v0.5.0...v0.6.0
|
103
120
|
[v0.5.0]: https://github.com/piotrmurach/tty-prompt/compare/v0.4.0...v0.5.0
|
data/Gemfile
CHANGED
data/README.md
CHANGED
@@ -51,7 +51,7 @@ Or install it yourself as:
|
|
51
51
|
* [2.2.5 modify](#225-modify)
|
52
52
|
* [2.2.6 required](#226-required)
|
53
53
|
* [2.2.7 validate](#227-validate)
|
54
|
-
* [2.2.8 messages](#228-messages)
|
54
|
+
* [2.2.8 error messages](#228-error-messages)
|
55
55
|
* [2.2.9 prefix](#229-prefix)
|
56
56
|
* [2.2.10 active_color](#2210-active_color)
|
57
57
|
* [2.2.11 help_color](#2211-help_color)
|
@@ -71,7 +71,7 @@ Or install it yourself as:
|
|
71
71
|
* [2.12.1 ok](#2121-ok)
|
72
72
|
* [2.12.2 warn](#2122-warn)
|
73
73
|
* [2.12.3 error](#2123-error)
|
74
|
-
|
74
|
+
* [2.13 keyboard events](#213-keyboard-events)
|
75
75
|
|
76
76
|
## 1. Usage
|
77
77
|
|
@@ -322,7 +322,7 @@ The **TTY::Prompt** comes with bult-in validations for `:email` and you can use
|
|
322
322
|
prompt.ask('What is your email?') { |q| q.validate :email }
|
323
323
|
```
|
324
324
|
|
325
|
-
#### 2.2.8 messages
|
325
|
+
#### 2.2.8 error messages
|
326
326
|
|
327
327
|
By default `tty-prompt` comes with predefined error messages for `required`, `in`, `validate` options. You can change these and configure to your liking either by inling them with the option:
|
328
328
|
|
@@ -341,6 +341,15 @@ prompt.ask('What is your email?') do |q|
|
|
341
341
|
end
|
342
342
|
```
|
343
343
|
|
344
|
+
to change default range validation error message do:
|
345
|
+
|
346
|
+
```ruby
|
347
|
+
prompt.ask('How spicy on scale (1-5)? ') do |q|
|
348
|
+
q.in '1-5'
|
349
|
+
q.messages[:range?] = '%{value} out of expected range #{in}'
|
350
|
+
end
|
351
|
+
```
|
352
|
+
|
344
353
|
#### 2.2.9 prefix
|
345
354
|
|
346
355
|
You can prefix each question asked using the `:prefix` option. This option can be applied either globally for all prompts or individual for each one:
|
@@ -906,6 +915,58 @@ Print message(s) in red do:
|
|
906
915
|
prompt.error(...)
|
907
916
|
```
|
908
917
|
|
918
|
+
#### 2.13 keyboard events
|
919
|
+
|
920
|
+
All the prompt types, when a key is pressed, fire key press events. You can subscribe to listen to this events by calling `on` with type of event name.
|
921
|
+
|
922
|
+
```ruby
|
923
|
+
prompt.on(:keypress) { |event| ... }
|
924
|
+
```
|
925
|
+
|
926
|
+
The event object is yielded to a block whenever particular event fires. The event has `key` and `value` methods. Further, the `key` responds to following messages:
|
927
|
+
|
928
|
+
* `name` - the name of the event such as :up, :down, letter or digit
|
929
|
+
* `meta` - true if event is non-standard key associated
|
930
|
+
* `shift` - true if shift has been pressed with the key
|
931
|
+
* `ctrl` - true if ctrl has been pressed with the key
|
932
|
+
|
933
|
+
For example, to add vim like key navigation to `select` prompt one would do the following:
|
934
|
+
|
935
|
+
```ruby
|
936
|
+
prompt.on(:keypress) do |event|
|
937
|
+
if event.key.name == 'j'
|
938
|
+
prompt.publish(:keydown)
|
939
|
+
end
|
940
|
+
|
941
|
+
if event.key.name == 'k'
|
942
|
+
prompt.publish(:keyup)
|
943
|
+
end
|
944
|
+
end
|
945
|
+
```
|
946
|
+
|
947
|
+
You can subscribe to more than one event:
|
948
|
+
|
949
|
+
```ruby
|
950
|
+
prompt.on(:keypress) { |key| ... }
|
951
|
+
.on(:keydown) { |key| ... }
|
952
|
+
```
|
953
|
+
|
954
|
+
The available events are:
|
955
|
+
|
956
|
+
* `:keypress`
|
957
|
+
* `:keydown`
|
958
|
+
* `:keyup`
|
959
|
+
* `:keyleft`
|
960
|
+
* `:keyright`
|
961
|
+
* `:keynum`
|
962
|
+
* `:keytab`
|
963
|
+
* `:keyenter`
|
964
|
+
* `:keyreturn`
|
965
|
+
* `:keyspace`
|
966
|
+
* `:keyescape`
|
967
|
+
* `:keydelete`
|
968
|
+
* `:keybackspace`
|
969
|
+
|
909
970
|
## Contributing
|
910
971
|
|
911
972
|
1. Fork it ( https://github.com/piotrmurach/tty-prompt/fork )
|
data/examples/ask.rb
CHANGED
@@ -5,3 +5,11 @@ require 'tty-prompt'
|
|
5
5
|
prompt = TTY::Prompt.new
|
6
6
|
|
7
7
|
prompt.ask('What is your name?', default: ENV['USER'])
|
8
|
+
|
9
|
+
prompt.ask('Folder name?') do |q|
|
10
|
+
q.required(true)
|
11
|
+
q.validate ->(v) { return !Dir.exist?(v) }
|
12
|
+
q.messages[:valid?] = 'Folder already exists?'
|
13
|
+
q.messages[:required?] = 'Folder name must not be empty'
|
14
|
+
end
|
15
|
+
|
data/examples/select.rb
CHANGED
@@ -5,4 +5,14 @@ require 'tty-prompt'
|
|
5
5
|
prompt = TTY::Prompt.new
|
6
6
|
|
7
7
|
warriors = %w(Scorpion Kano Jax)
|
8
|
+
|
9
|
+
prompt.on(:keypress) do |event|
|
10
|
+
if event.key.name == 'j'
|
11
|
+
prompt.publish(:keydown)
|
12
|
+
end
|
13
|
+
if event.key.name == 'k'
|
14
|
+
prompt.publish(:keyup)
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
8
18
|
prompt.select('Choose your destiny?', warriors)
|
data/lib/tty/prompt.rb
CHANGED
@@ -49,8 +49,8 @@ module TTY
|
|
49
49
|
def_delegators :@cursor, :clear_lines, :clear_line,
|
50
50
|
:show, :hide
|
51
51
|
|
52
|
-
def_delegators :@reader, :read_line, :read_keypress,
|
53
|
-
:read_multiline, :on, :subscribe
|
52
|
+
def_delegators :@reader, :read_char, :read_line, :read_keypress,
|
53
|
+
:read_multiline, :on, :subscribe, :publish
|
54
54
|
|
55
55
|
def_delegators :@output, :print, :puts, :flush
|
56
56
|
|
@@ -98,7 +98,7 @@ module TTY
|
|
98
98
|
# @api public
|
99
99
|
def ask(message, *args, &block)
|
100
100
|
options = Utils.extract_options!(args)
|
101
|
-
options.merge!(self.class.messages)
|
101
|
+
options.merge!({messages: self.class.messages})
|
102
102
|
question = Question.new(self, options)
|
103
103
|
question.call(message, &block)
|
104
104
|
end
|
@@ -49,7 +49,7 @@ module TTY
|
|
49
49
|
# Check if input requires validation
|
50
50
|
class CheckValidation
|
51
51
|
def self.call(question, value)
|
52
|
-
if !question.validation? ||
|
52
|
+
if !question.validation? || (question.required? && value.nil?) ||
|
53
53
|
(question.validation? &&
|
54
54
|
Validation.new(question.validation).call(value))
|
55
55
|
[value]
|
data/lib/tty/prompt/reader.rb
CHANGED
@@ -86,15 +86,18 @@ module TTY
|
|
86
86
|
|
87
87
|
# Reads single character including invisible multibyte codes
|
88
88
|
#
|
89
|
+
# @params [Integer] bytes
|
90
|
+
# the number of bytes to read
|
91
|
+
#
|
89
92
|
# @return [String]
|
90
93
|
#
|
91
94
|
# @api public
|
92
|
-
def read_char
|
93
|
-
chars = input.
|
95
|
+
def read_char(bytes = 1)
|
96
|
+
chars = input.readpartial(bytes)
|
94
97
|
while CSI.start_with?(chars) ||
|
95
98
|
chars.start_with?(CSI) &&
|
96
99
|
!(64..126).include?(chars.each_codepoint.to_a.last)
|
97
|
-
next_char = read_char
|
100
|
+
next_char = read_char(bytes + 1)
|
98
101
|
chars << next_char
|
99
102
|
end
|
100
103
|
chars
|
@@ -4,7 +4,7 @@ module TTY
|
|
4
4
|
class Prompt
|
5
5
|
class Reader
|
6
6
|
module Codes
|
7
|
-
BACKSPACE = "\
|
7
|
+
BACKSPACE = "\x7f"
|
8
8
|
DELETE = "\004"
|
9
9
|
ESCAPE = "\e"
|
10
10
|
LINEFEED = "\n"
|
@@ -12,23 +12,35 @@ module TTY
|
|
12
12
|
SPACE = " "
|
13
13
|
TAB = "\t"
|
14
14
|
|
15
|
-
KEY_UP = "
|
16
|
-
KEY_DOWN = "
|
17
|
-
KEY_RIGHT = "
|
18
|
-
KEY_LEFT = "
|
19
|
-
KEY_CLEAR = "
|
20
|
-
KEY_END = "
|
21
|
-
KEY_HOME = "
|
22
|
-
KEY_DELETE = "
|
15
|
+
KEY_UP = "[A"
|
16
|
+
KEY_DOWN = "[B"
|
17
|
+
KEY_RIGHT = "[C"
|
18
|
+
KEY_LEFT = "[D"
|
19
|
+
KEY_CLEAR = "[E"
|
20
|
+
KEY_END = "[F"
|
21
|
+
KEY_HOME = "[H"
|
22
|
+
KEY_DELETE = "[3"
|
23
23
|
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
24
|
+
KEY_UP_XTERM = "OA"
|
25
|
+
KEY_DOWN_XTERM = "OB"
|
26
|
+
KEY_RIGHT_XTERM = "OC"
|
27
|
+
KEY_LEFT_XTERM = "OD"
|
28
|
+
KEY_CLEAR_XTERM = "OE"
|
29
|
+
KEY_END_XTERM = "OF"
|
30
|
+
KEY_HOME_XTERM = "OH"
|
31
|
+
KEY_DELETE_XTERM = "O3"
|
32
|
+
|
33
|
+
KEY_UP_SHIFT = "[a"
|
34
|
+
KEY_DOWN_SHIFT = "[b"
|
35
|
+
KEY_RIGHT_SHIFT = "[c"
|
36
|
+
KEY_LEFT_SHIFT = "[d"
|
37
|
+
KEY_CLEAR_SHIFT = "[e"
|
38
|
+
|
39
|
+
KEY_UP_CTRL = "0a"
|
40
|
+
KEY_DOWN_CTRL = "0b"
|
41
|
+
KEY_RIGHT_CTRL = "0c"
|
42
|
+
KEY_LEFT_CTRL = "0d"
|
43
|
+
KEY_CLEAR_CTRL = "0e"
|
32
44
|
|
33
45
|
CTRL_J = "\x0A"
|
34
46
|
CTRL_N = "\x0E"
|
@@ -39,30 +51,30 @@ module TTY
|
|
39
51
|
CTRL_H = "\b"
|
40
52
|
CTRL_L = "\f"
|
41
53
|
|
42
|
-
F1_XTERM = "
|
43
|
-
F2_XTERM = "
|
44
|
-
F3_XTERM = "
|
45
|
-
F4_XTERM = "
|
54
|
+
F1_XTERM = "OP"
|
55
|
+
F2_XTERM = "OQ"
|
56
|
+
F3_XTERM = "OR"
|
57
|
+
F4_XTERM = "OS"
|
46
58
|
|
47
|
-
F1_GNOME = "
|
48
|
-
F2_GNOME = "
|
49
|
-
F3_GNOME = "
|
50
|
-
F4_GNOME = "
|
59
|
+
F1_GNOME = "[11~"
|
60
|
+
F2_GNOME = "[12~"
|
61
|
+
F3_GNOME = "[13~"
|
62
|
+
F4_GNOME = "[14~"
|
51
63
|
|
52
|
-
F1_WIN = "
|
53
|
-
F2_WIN = "
|
54
|
-
F3_WIN = "
|
55
|
-
F4_WIN = "
|
56
|
-
F5_WIN = "
|
64
|
+
F1_WIN = "[[A"
|
65
|
+
F2_WIN = "[[B"
|
66
|
+
F3_WIN = "[[C"
|
67
|
+
F4_WIN = "[[D"
|
68
|
+
F5_WIN = "[[E"
|
57
69
|
|
58
|
-
F5 = "
|
59
|
-
F6 = "
|
60
|
-
F7 = "
|
61
|
-
F8 = "
|
62
|
-
F9 = "
|
63
|
-
F10 = "
|
64
|
-
F11 = "
|
65
|
-
F12 = "
|
70
|
+
F5 = "[15~"
|
71
|
+
F6 = "[17~"
|
72
|
+
F7 = "[18~"
|
73
|
+
F8 = "[19~"
|
74
|
+
F9 = "[20~"
|
75
|
+
F10 = "[21~"
|
76
|
+
F11 = "[23~"
|
77
|
+
F12 = "[24~"
|
66
78
|
end # Codes
|
67
79
|
end # Reader
|
68
80
|
end # Prompt
|
@@ -21,10 +21,11 @@ module TTY
|
|
21
21
|
#
|
22
22
|
# @api public
|
23
23
|
class KeyEvent < Struct.new(:value, :key)
|
24
|
-
META_KEY_CODE_RE = /^(?:\e)(O|N|\[|\[\[)(?:(\d+)(?:;(\d+))?([~^$])|(?:1;)?(\d+)?([a-zA-Z]))/
|
24
|
+
META_KEY_CODE_RE = /^(?:\e+)(O|N|\[|\[\[)(?:(\d+)(?:;(\d+))?([~^$])|(?:1;)?(\d+)?([a-zA-Z]))/
|
25
25
|
|
26
26
|
def self.from(char)
|
27
27
|
key = Key.new
|
28
|
+
|
28
29
|
case char
|
29
30
|
when Codes::RETURN then key.name = :return
|
30
31
|
when Codes::LINEFEED then key.name = :enter
|
@@ -33,9 +34,14 @@ module TTY
|
|
33
34
|
"#{Codes::ESCAPE}#{Codes::BACKSPACE}",
|
34
35
|
"#{Codes::ESCAPE}#{Codes::CTRL_H}"
|
35
36
|
key.name = :backspace
|
37
|
+
key.meta = (char.chars.to_a[0] == Codes::ESCAPE)
|
36
38
|
when Codes::DELETE then key.name = :delete
|
37
|
-
when Codes::SPACE
|
38
|
-
|
39
|
+
when Codes::SPACE, "#{Codes::ESCAPE}#{Codes::SPACE}"
|
40
|
+
key.name = :space
|
41
|
+
key.meta = (char.size == 2)
|
42
|
+
when Codes::ESCAPE, "#{Codes::ESCAPE}#{Codes::ESCAPE}"
|
43
|
+
key.name = :escape
|
44
|
+
key.meta = (char.size == 2)
|
39
45
|
when proc { |c| c.length == 1 && c =~ /[a-z]/ }
|
40
46
|
key.name = char
|
41
47
|
when proc { |c| c.length == 1 && c =~ /[A-Z]/ }
|
@@ -44,15 +50,21 @@ module TTY
|
|
44
50
|
when /^\d+$/
|
45
51
|
key.name = :num
|
46
52
|
when META_KEY_CODE_RE # ansi escape
|
47
|
-
|
53
|
+
parts = META_KEY_CODE_RE.match(char)
|
54
|
+
code = "#{parts[1]}#{parts[2]}#{parts[4]}#{parts[6]}"
|
55
|
+
modifier = (parts[3] || parts[5] || 1) - 1
|
56
|
+
|
57
|
+
key.ctrl = (modifier & 4) != 0
|
58
|
+
key.meta = (modifier & 10) != 0
|
59
|
+
key.shift = (modifier & 1) != 0
|
48
60
|
|
49
|
-
case
|
61
|
+
case code
|
50
62
|
# f1 - f12
|
51
63
|
when Codes::F1_XTERM, Codes::F1_GNOME, Codes::F1_WIN then key.name = :f1
|
52
64
|
when Codes::F2_XTERM, Codes::F2_GNOME, Codes::F2_WIN then key.name = :f2
|
53
65
|
when Codes::F3_XTERM, Codes::F3_GNOME, Codes::F3_WIN then key.name = :f3
|
54
66
|
when Codes::F4_XTERM, Codes::F4_GNOME, Codes::F4_WIN then key.name = :f4
|
55
|
-
when Codes::F5 then key.name = :f5
|
67
|
+
when Codes::F5, Codes::F5_WIN then key.name = :f5
|
56
68
|
when Codes::F6 then key.name = :f6
|
57
69
|
when Codes::F7 then key.name = :f7
|
58
70
|
when Codes::F8 then key.name = :f8
|
@@ -61,19 +73,37 @@ module TTY
|
|
61
73
|
when Codes::F11 then key.name = :f11
|
62
74
|
when Codes::F12 then key.name = :f12
|
63
75
|
# navigation
|
64
|
-
when Codes::KEY_UP, Codes::
|
76
|
+
when Codes::KEY_UP, Codes::KEY_UP_XTERM,
|
77
|
+
Codes::CTRL_K, Codes::CTRL_P
|
65
78
|
key.name = :up
|
66
|
-
when Codes::
|
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
|
67
84
|
key.name = :down
|
68
|
-
when Codes::
|
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
|
69
89
|
key.name = :right
|
70
|
-
when Codes::
|
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
|
71
94
|
key.name = :left
|
72
|
-
when Codes::
|
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
|
73
99
|
key.name = :clear
|
74
|
-
when Codes::
|
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
|
75
104
|
key.name = :end
|
76
|
-
|
105
|
+
|
106
|
+
when Codes::KEY_HOME, Codes::KEY_HOME_XTERM
|
77
107
|
key.name = :home
|
78
108
|
end
|
79
109
|
end
|
data/lib/tty/prompt/version.rb
CHANGED
@@ -7,16 +7,20 @@ RSpec.describe TTY::Prompt::Question, '#in' do
|
|
7
7
|
it "reads range from option" do
|
8
8
|
prompt.input << '8'
|
9
9
|
prompt.input.rewind
|
10
|
+
|
10
11
|
answer = prompt.ask("How do you like it on scale 1-10?", in: '1-10')
|
12
|
+
|
11
13
|
expect(answer).to eq('8')
|
12
14
|
end
|
13
15
|
|
14
16
|
it 'reads number within string range' do
|
15
17
|
prompt.input << '8'
|
16
18
|
prompt.input.rewind
|
19
|
+
|
17
20
|
answer = prompt.ask("How do you like it on scale 1-10?") do |q|
|
18
21
|
q.in('1-10')
|
19
22
|
end
|
23
|
+
|
20
24
|
expect(answer).to eq('8')
|
21
25
|
expect(prompt.output.string).to eq([
|
22
26
|
"How do you like it on scale 1-10? ",
|
@@ -29,9 +33,11 @@ RSpec.describe TTY::Prompt::Question, '#in' do
|
|
29
33
|
it 'reads number within digit range' do
|
30
34
|
prompt.input << '8.1'
|
31
35
|
prompt.input.rewind
|
36
|
+
|
32
37
|
answer = prompt.ask("How do you like it on scale 1-10?") do |q|
|
33
38
|
q.in(1.0..11.5)
|
34
39
|
end
|
40
|
+
|
35
41
|
expect(answer).to eq('8.1')
|
36
42
|
expect(prompt.output.string).to eq([
|
37
43
|
"How do you like it on scale 1-10? ",
|
@@ -44,9 +50,11 @@ RSpec.describe TTY::Prompt::Question, '#in' do
|
|
44
50
|
it 'reads letters within range' do
|
45
51
|
prompt.input << 'E'
|
46
52
|
prompt.input.rewind
|
53
|
+
|
47
54
|
answer = prompt.ask("Your favourite vitamin? (A-K)") do |q|
|
48
55
|
q.in('A-K')
|
49
56
|
end
|
57
|
+
|
50
58
|
expect(answer).to eq('E')
|
51
59
|
expect(prompt.output.string).to eq([
|
52
60
|
"Your favourite vitamin? (A-K) ",
|
@@ -55,4 +63,45 @@ RSpec.describe TTY::Prompt::Question, '#in' do
|
|
55
63
|
"Your favourite vitamin? (A-K) \e[32mE\e[0m\n"
|
56
64
|
].join)
|
57
65
|
end
|
66
|
+
|
67
|
+
it "provides default error message when wrong input" do
|
68
|
+
prompt.input << "A\n2\n"
|
69
|
+
prompt.input.rewind
|
70
|
+
|
71
|
+
answer = prompt.ask("How spicy on scale? (1-5)", in: '1-5')
|
72
|
+
|
73
|
+
expect(answer).to eq('2')
|
74
|
+
expect(prompt.output.string).to eq([
|
75
|
+
"How spicy on scale? (1-5) ",
|
76
|
+
"\e[1000D\e[K",
|
77
|
+
"\e[31m>>\e[0m Value A must be within the range 1..5\e[1A",
|
78
|
+
"\e[1000D\e[K",
|
79
|
+
"How spicy on scale? (1-5) ",
|
80
|
+
"\e[1000D\e[K\e[1A",
|
81
|
+
"\e[1000D\e[K",
|
82
|
+
"How spicy on scale? (1-5) \e[32m2\e[0m\n"
|
83
|
+
].join)
|
84
|
+
end
|
85
|
+
|
86
|
+
it "overwrites default error message when wrong input" do
|
87
|
+
prompt.input << "A\n2\n"
|
88
|
+
prompt.input.rewind
|
89
|
+
|
90
|
+
answer = prompt.ask("How spicy on scale? (1-5)") do |q|
|
91
|
+
q.in '1-5'
|
92
|
+
q.messages[:range?] = 'Ohh dear what is this %{value} doing in %{in}?'
|
93
|
+
end
|
94
|
+
|
95
|
+
expect(answer).to eq('2')
|
96
|
+
expect(prompt.output.string).to eq([
|
97
|
+
"How spicy on scale? (1-5) ",
|
98
|
+
"\e[1000D\e[K",
|
99
|
+
"\e[31m>>\e[0m Ohh dear what is this A doing in 1..5?\e[1A",
|
100
|
+
"\e[1000D\e[K",
|
101
|
+
"How spicy on scale? (1-5) ",
|
102
|
+
"\e[1000D\e[K\e[1A",
|
103
|
+
"\e[1000D\e[K",
|
104
|
+
"How spicy on scale? (1-5) \e[32m2\e[0m\n"
|
105
|
+
].join)
|
106
|
+
end
|
58
107
|
end
|
@@ -17,10 +17,14 @@ RSpec.describe TTY::Prompt::Question, '#required' do
|
|
17
17
|
end
|
18
18
|
|
19
19
|
it 'requires value to be present with option' do
|
20
|
-
prompt.input << "
|
20
|
+
prompt.input << " \nPiotr"
|
21
21
|
prompt.input.rewind
|
22
|
-
prompt.ask('What is your name?'
|
22
|
+
prompt.ask('What is your name?', required: true)
|
23
23
|
expect(prompt.output.string).to eq([
|
24
|
+
"What is your name? ",
|
25
|
+
"\e[1000D\e[K",
|
26
|
+
"\e[31m>>\e[0m Value must be provided\e[1A",
|
27
|
+
"\e[1000D\e[K",
|
24
28
|
"What is your name? ",
|
25
29
|
"\e[1000D\e[K\e[1A",
|
26
30
|
"\e[1000D\e[K",
|
@@ -34,4 +38,30 @@ RSpec.describe TTY::Prompt::Question, '#required' do
|
|
34
38
|
answer = prompt.ask('What is your name?') { |q| q.required(false) }
|
35
39
|
expect(answer).to be_nil
|
36
40
|
end
|
41
|
+
|
42
|
+
it "uses required in validation check" do
|
43
|
+
prompt.input << " \n#{__FILE__}\ntest\n"
|
44
|
+
prompt.input.rewind
|
45
|
+
answer = prompt.ask('File name?') do |q|
|
46
|
+
q.required(true)
|
47
|
+
q.validate { |v| !File.exist?(v) }
|
48
|
+
q.messages[:required?] = 'File name must not be empty!'
|
49
|
+
q.messages[:valid?] = 'File already exists!'
|
50
|
+
end
|
51
|
+
expect(prompt.output.string).to eq([
|
52
|
+
"File name? ",
|
53
|
+
"\e[1000D\e[K",
|
54
|
+
"\e[31m>>\e[0m File name must not be empty!",
|
55
|
+
"\e[1A\e[1000D\e[K",
|
56
|
+
"File name? ",
|
57
|
+
"\e[1000D\e[K",
|
58
|
+
"\e[31m>>\e[0m File already exists!",
|
59
|
+
"\e[1A\e[1000D\e[K",
|
60
|
+
"File name? ",
|
61
|
+
"\e[1000D\e[K",
|
62
|
+
"\e[1A\e[1000D\e[K",
|
63
|
+
"File name? \e[32mtest\e[0m\n",
|
64
|
+
].join)
|
65
|
+
expect(answer).to eq('test')
|
66
|
+
end
|
37
67
|
end
|
@@ -7,9 +7,11 @@ RSpec.describe TTY::Prompt::Question, '#validate' do
|
|
7
7
|
it 'validates input with regex' do
|
8
8
|
prompt.input << 'piotr.murach'
|
9
9
|
prompt.input.rewind
|
10
|
+
|
10
11
|
answer = prompt.ask('What is your username?') do |q|
|
11
12
|
q.validate(/^[^\.]+\.[^\.]+/)
|
12
13
|
end
|
14
|
+
|
13
15
|
expect(answer).to eq('piotr.murach')
|
14
16
|
expect(prompt.output.string).to eq([
|
15
17
|
"What is your username? ",
|
@@ -22,18 +24,65 @@ RSpec.describe TTY::Prompt::Question, '#validate' do
|
|
22
24
|
it 'validates input with proc' do
|
23
25
|
prompt.input << 'piotr.murach'
|
24
26
|
prompt.input.rewind
|
27
|
+
|
25
28
|
answer = prompt.ask('What is your username?') do |q|
|
26
29
|
q.validate { |input| input =~ /^[^\.]+\.[^\.]+/ }
|
27
30
|
end
|
31
|
+
|
28
32
|
expect(answer).to eq('piotr.murach')
|
29
33
|
end
|
30
34
|
|
31
35
|
it 'understands custom validation like :email' do
|
32
36
|
prompt.input << 'piotr@example.com'
|
33
37
|
prompt.input.rewind
|
38
|
+
|
39
|
+
answer = prompt.ask('What is your email?') do |q|
|
40
|
+
q.validate :email
|
41
|
+
end
|
42
|
+
|
43
|
+
expect(answer).to eq('piotr@example.com')
|
44
|
+
end
|
45
|
+
|
46
|
+
it "provides default error message for wrong input" do
|
47
|
+
prompt.input << "invalid\npiotr@example.com"
|
48
|
+
prompt.input.rewind
|
49
|
+
|
50
|
+
answer = prompt.ask('What is your email?') do |q|
|
51
|
+
q.validate :email
|
52
|
+
end
|
53
|
+
|
54
|
+
expect(answer).to eq('piotr@example.com')
|
55
|
+
expect(prompt.output.string).to eq([
|
56
|
+
"What is your email? ",
|
57
|
+
"\e[1000D\e[K",
|
58
|
+
"\e[31m>>\e[0m Your answer is invalid (must match :email)\e[1A",
|
59
|
+
"\e[1000D\e[K",
|
60
|
+
"What is your email? ",
|
61
|
+
"\e[1000D\e[K\e[1A",
|
62
|
+
"\e[1000D\e[K",
|
63
|
+
"What is your email? \e[32mpiotr@example.com\e[0m\n"
|
64
|
+
].join)
|
65
|
+
end
|
66
|
+
|
67
|
+
it "provides custom error message for wrong input" do
|
68
|
+
prompt.input << "invalid\npiotr@example.com"
|
69
|
+
prompt.input.rewind
|
70
|
+
|
34
71
|
answer = prompt.ask('What is your email?') do |q|
|
35
72
|
q.validate :email
|
73
|
+
q.messages[:valid?] = 'Not an email!'
|
36
74
|
end
|
75
|
+
|
37
76
|
expect(answer).to eq('piotr@example.com')
|
77
|
+
expect(prompt.output.string).to eq([
|
78
|
+
"What is your email? ",
|
79
|
+
"\e[1000D\e[K",
|
80
|
+
"\e[31m>>\e[0m Not an email!\e[1A",
|
81
|
+
"\e[1000D\e[K",
|
82
|
+
"What is your email? ",
|
83
|
+
"\e[1000D\e[K\e[1A",
|
84
|
+
"\e[1000D\e[K",
|
85
|
+
"What is your email? \e[32mpiotr@example.com\e[0m\n"
|
86
|
+
].join)
|
38
87
|
end
|
39
88
|
end
|
@@ -1,13 +1,21 @@
|
|
1
1
|
# encoding: utf-8
|
2
2
|
|
3
|
-
|
3
|
+
require 'shellwords'
|
4
4
|
|
5
|
+
RSpec.describe TTY::Prompt::Reader::KeyEvent, '#from' do
|
5
6
|
it "parses ctrl+h" do
|
6
7
|
event = described_class.from("\b")
|
7
8
|
expect(event.key.name).to eq(:backspace)
|
8
9
|
expect(event.value).to eq("\b")
|
9
10
|
end
|
10
11
|
|
12
|
+
it "parses backspace" do
|
13
|
+
event = described_class.from("\e\x7f")
|
14
|
+
expect(event.key.name).to eq(:backspace)
|
15
|
+
expect(event.key.meta).to eq(true)
|
16
|
+
expect(event.value).to eq("\e\x7f")
|
17
|
+
end
|
18
|
+
|
11
19
|
it "parses lowercase char" do
|
12
20
|
event = described_class.from('a')
|
13
21
|
expect(event.key.name).to eq('a')
|
@@ -20,48 +28,69 @@ RSpec.describe TTY::Prompt::Reader::KeyEvent, '::from' do
|
|
20
28
|
expect(event.value).to eq('A')
|
21
29
|
end
|
22
30
|
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
it "parses left key" do
|
49
|
-
event = described_class.from("\e[D")
|
50
|
-
expect(event.key.name).to eq(:left)
|
51
|
-
end
|
52
|
-
|
53
|
-
it "parses clear key" do
|
54
|
-
event = described_class.from("\e[E")
|
55
|
-
expect(event.key.name).to eq(:clear)
|
31
|
+
# F1-F12 keys
|
32
|
+
{
|
33
|
+
f1: ["\eOP", "\e[11~", "\e[[A"],
|
34
|
+
f2: ["\eOQ", "\e[12~", "\e[[B"],
|
35
|
+
f3: ["\eOR", "\e[13~", "\e[[C"],
|
36
|
+
f4: ["\eOS", "\e[14~", "\e[[D"],
|
37
|
+
f5: [ "\e[15~", "\e[[E"],
|
38
|
+
f6: [ "\e[17~" ],
|
39
|
+
f7: [ "\e[18~" ],
|
40
|
+
f8: [ "\e[19~" ],
|
41
|
+
f9: [ "\e[20~" ],
|
42
|
+
f10: [ "\e[21~" ],
|
43
|
+
f11: [ "\e[23~" ],
|
44
|
+
f12: [ "\e[24~" ]
|
45
|
+
}.each do |name, codes|
|
46
|
+
codes.each do |code|
|
47
|
+
it "parses #{Shellwords.escape(code)} as #{name} key" do
|
48
|
+
event = described_class.from(code)
|
49
|
+
expect(event.key.name).to eq(name)
|
50
|
+
expect(event.key.meta).to eq(false)
|
51
|
+
expect(event.key.ctrl).to eq(false)
|
52
|
+
expect(event.key.shift).to eq(false)
|
53
|
+
end
|
54
|
+
end
|
56
55
|
end
|
57
56
|
|
58
|
-
|
59
|
-
|
60
|
-
|
57
|
+
# arrow keys & page navigation
|
58
|
+
#
|
59
|
+
{
|
60
|
+
up: ["\e[A", "\eOA"],
|
61
|
+
down: ["\e[B", "\eOB"],
|
62
|
+
right: ["\e[C", "\eOC"],
|
63
|
+
left: ["\e[D", "\eOD"],
|
64
|
+
clear: ["\e[E", "\eOE"],
|
65
|
+
end: ["\e[F"],
|
66
|
+
home: ["\e[H"]
|
67
|
+
}.each do |name, codes|
|
68
|
+
codes.each do |code|
|
69
|
+
it "parses #{Shellwords.escape(code)} as #{name} key" do
|
70
|
+
event = described_class.from(code)
|
71
|
+
expect(event.key.name).to eq(name)
|
72
|
+
expect(event.key.meta).to eq(false)
|
73
|
+
expect(event.key.ctrl).to eq(false)
|
74
|
+
expect(event.key.shift).to eq(false)
|
75
|
+
end
|
76
|
+
end
|
61
77
|
end
|
62
78
|
|
63
|
-
|
64
|
-
|
65
|
-
|
79
|
+
{
|
80
|
+
up: ["\e[a"],
|
81
|
+
down: ["\e[b"],
|
82
|
+
right: ["\e[c"],
|
83
|
+
left: ["\e[d"],
|
84
|
+
clear: ["\e[e"],
|
85
|
+
}.each do |name, codes|
|
86
|
+
codes.each do |code|
|
87
|
+
it "parses #{Shellwords.escape(code)} as SHIFT + #{name} key" do
|
88
|
+
event = described_class.from(code)
|
89
|
+
expect(event.key.name).to eq(name)
|
90
|
+
expect(event.key.meta).to eq(false)
|
91
|
+
expect(event.key.ctrl).to eq(false)
|
92
|
+
expect(event.key.shift).to eq(true)
|
93
|
+
end
|
94
|
+
end
|
66
95
|
end
|
67
96
|
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: tty-prompt
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.8.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Piotr Murach
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2016-
|
11
|
+
date: 2016-11-29 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: necromancer
|
@@ -139,6 +139,7 @@ files:
|
|
139
139
|
- examples/enum_select.rb
|
140
140
|
- examples/expand.rb
|
141
141
|
- examples/in.rb
|
142
|
+
- examples/keypress.rb
|
142
143
|
- examples/mask.rb
|
143
144
|
- examples/multi_select.rb
|
144
145
|
- examples/select.rb
|