tty-prompt 0.7.1 → 0.8.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 +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
|