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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: da6d9da4a55660577a3f5c65c4058c666defd1d7
4
- data.tar.gz: 7d647c6c08f2d3271b4660e3da777885001cc772
3
+ metadata.gz: f9a360c4b3a7cd670868b20f7b220223274f555b
4
+ data.tar.gz: 17667527540e13b4e91eca3c042c9fcc5cb24b86
5
5
  SHA512:
6
- metadata.gz: 876eea0fd3fea3da77b4c2730247807022fc2553fa7475ca089d099c961824818ee59370129f10520b9a29402bbecc3b912b1ed5e5e9fb9c583643561d6aeab1
7
- data.tar.gz: 9b3a34ab5221a8305a6608b48cb1b6567cb6bfbd77e562dfd3dda540e63816978212380228570ba290e98c2e5a70f7b67a2877bb560482082040bf07cf2f4d18
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.0
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
@@ -6,6 +6,7 @@ group :test do
6
6
  gem 'benchmark-ips', '~> 2.0.0'
7
7
  gem 'simplecov', '~> 0.10.0'
8
8
  gem 'coveralls', '~> 0.8.2'
9
+ gem 'term-ansicolor', '=1.3.2'
9
10
  end
10
11
 
11
12
  group :tools do
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
+
@@ -0,0 +1,11 @@
1
+ # encoding: utf-8
2
+
3
+ require 'tty-prompt'
4
+
5
+ prompt = TTY::Prompt::new(interrupt: :exit)
6
+
7
+ prompt.on(:keypress) do |event|
8
+ puts "name: #{event.key.name}, value: #{event.value.dump}"
9
+ end
10
+
11
+ prompt.read_keypress
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]
@@ -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.sysread(1)
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 = "\177"
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 = "\e[A"
16
- KEY_DOWN = "\e[B"
17
- KEY_RIGHT = "\e[C"
18
- KEY_LEFT = "\e[D"
19
- KEY_CLEAR = "\e[E"
20
- KEY_END = "\e[F"
21
- KEY_HOME = "\e[H"
22
- KEY_DELETE = "\e[3"
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
- KEY_UP_ALT = "\eOA"
25
- KEY_DOWN_ALT = "\eOB"
26
- KEY_RIGHT_ALT = "\eOC"
27
- KEY_LEFT_ALT = "\eOD"
28
- KEY_CLEAR_ALT = "\eOE"
29
- KEY_END_ALT = "\eOF"
30
- KEY_HOME_ALT = "\eOH"
31
- KEY_DELETE_ALT = "\eO3"
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 = "\eOP"
43
- F2_XTERM = "\eOQ"
44
- F3_XTERM = "\eOR"
45
- F4_XTERM = "\eOS"
54
+ F1_XTERM = "OP"
55
+ F2_XTERM = "OQ"
56
+ F3_XTERM = "OR"
57
+ F4_XTERM = "OS"
46
58
 
47
- F1_GNOME = "\e[11~"
48
- F2_GNOME = "\e[12~"
49
- F3_GNOME = "\e[13~"
50
- F4_GNOME = "\e[14~"
59
+ F1_GNOME = "[11~"
60
+ F2_GNOME = "[12~"
61
+ F3_GNOME = "[13~"
62
+ F4_GNOME = "[14~"
51
63
 
52
- F1_WIN = "\e[[A"
53
- F2_WIN = "\e[[B"
54
- F3_WIN = "\e[[C"
55
- F4_WIN = "\e[[D"
56
- F5_WIN = "\e[[E"
64
+ F1_WIN = "[[A"
65
+ F2_WIN = "[[B"
66
+ F3_WIN = "[[C"
67
+ F4_WIN = "[[D"
68
+ F5_WIN = "[[E"
57
69
 
58
- F5 = "\e[15~"
59
- F6 = "\e[17~"
60
- F7 = "\e[18~"
61
- F8 = "\e[19~"
62
- F9 = "\e[20~"
63
- F10 = "\e[21~"
64
- F11 = "\e[23~"
65
- F12 = "\e[24~"
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 then key.name = :space
38
- when Codes::CTRL_C, Codes::ESCAPE then key.name = :escape
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
- key.meta = true
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 char
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::KEY_UP_ALT, Codes::CTRL_K, Codes::CTRL_P
76
+ when Codes::KEY_UP, Codes::KEY_UP_XTERM,
77
+ Codes::CTRL_K, Codes::CTRL_P
65
78
  key.name = :up
66
- when Codes::KEY_DOWN, Codes::KEY_DOWN_ALT, Codes::CTRL_J, Codes::CTRL_N
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::KEY_RIGHT, Codes::KEY_RIGHT_ALT, Codes::CTRL_L
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::KEY_LEFT, Codes::KEY_LEFT_ALT, Codes::CTRL_H
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::KEY_CLEAR, Codes::KEY_CLEAR_ALT
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::KEY_END, Codes::KEY_END_ALT
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
- when Codes::KEY_HOME, Codes::KEY_HOME_ALT
105
+
106
+ when Codes::KEY_HOME, Codes::KEY_HOME_XTERM
77
107
  key.name = :home
78
108
  end
79
109
  end
@@ -2,6 +2,6 @@
2
2
 
3
3
  module TTY
4
4
  class Prompt
5
- VERSION = "0.7.1"
5
+ VERSION = "0.8.0"
6
6
  end # Prompt
7
7
  end # TTY
@@ -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 << "Piotr"
20
+ prompt.input << " \nPiotr"
21
21
  prompt.input.rewind
22
- prompt.ask('What is your name?') { |q| q.required(true) }
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
- RSpec.describe TTY::Prompt::Reader::KeyEvent, '::from' do
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
- it "parses f5 key" do
24
- event = described_class.from("\e[15~")
25
- expect(event.key.name).to eq(:f5)
26
- end
27
-
28
- it "parses up key" do
29
- event = described_class.from("\e[A")
30
- expect(event.key.name).to eq(:up)
31
- end
32
-
33
- it "parses up key on gnome" do
34
- event = described_class.from("\eOA")
35
- expect(event.key.name).to eq(:up)
36
- end
37
-
38
- it "parses down key" do
39
- event = described_class.from("\e[B")
40
- expect(event.key.name).to eq(:down)
41
- end
42
-
43
- it "parses right key" do
44
- event = described_class.from("\e[C")
45
- expect(event.key.name).to eq(:right)
46
- end
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
- it "parses end key" do
59
- event = described_class.from("\e[F")
60
- expect(event.key.name).to eq(:end)
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
- it "parses home key" do
64
- event = described_class.from("\e[H")
65
- expect(event.key.name).to eq(:home)
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.7.1
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-08-07 00:00:00.000000000 Z
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