tty-prompt 0.12.0 → 0.13.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (75) hide show
  1. checksums.yaml +4 -4
  2. data/.travis.yml +1 -3
  3. data/CHANGELOG.md +12 -0
  4. data/README.md +3 -3
  5. data/appveyor.yml +2 -0
  6. data/lib/tty/prompt/keypress.rb +7 -4
  7. data/lib/tty/prompt/reader.rb +1 -1
  8. data/lib/tty/prompt/reader/win_api.rb +14 -0
  9. data/lib/tty/prompt/reader/win_console.rb +9 -2
  10. data/lib/tty/prompt/timeout.rb +44 -23
  11. data/lib/tty/prompt/version.rb +1 -1
  12. data/tty-prompt.gemspec +6 -3
  13. metadata +21 -131
  14. data/spec/spec_helper.rb +0 -45
  15. data/spec/unit/ask_spec.rb +0 -132
  16. data/spec/unit/choice/eql_spec.rb +0 -22
  17. data/spec/unit/choice/from_spec.rb +0 -29
  18. data/spec/unit/choices/add_spec.rb +0 -12
  19. data/spec/unit/choices/each_spec.rb +0 -13
  20. data/spec/unit/choices/find_by_spec.rb +0 -10
  21. data/spec/unit/choices/new_spec.rb +0 -10
  22. data/spec/unit/choices/pluck_spec.rb +0 -9
  23. data/spec/unit/collect_spec.rb +0 -33
  24. data/spec/unit/converters/convert_bool_spec.rb +0 -58
  25. data/spec/unit/converters/convert_char_spec.rb +0 -11
  26. data/spec/unit/converters/convert_custom_spec.rb +0 -14
  27. data/spec/unit/converters/convert_date_spec.rb +0 -34
  28. data/spec/unit/converters/convert_file_spec.rb +0 -17
  29. data/spec/unit/converters/convert_number_spec.rb +0 -39
  30. data/spec/unit/converters/convert_path_spec.rb +0 -18
  31. data/spec/unit/converters/convert_range_spec.rb +0 -22
  32. data/spec/unit/converters/convert_regex_spec.rb +0 -12
  33. data/spec/unit/converters/convert_string_spec.rb +0 -21
  34. data/spec/unit/converters/on_error_spec.rb +0 -9
  35. data/spec/unit/distance/distance_spec.rb +0 -73
  36. data/spec/unit/enum_paginator_spec.rb +0 -65
  37. data/spec/unit/enum_select_spec.rb +0 -310
  38. data/spec/unit/error_spec.rb +0 -20
  39. data/spec/unit/evaluator_spec.rb +0 -67
  40. data/spec/unit/expand_spec.rb +0 -198
  41. data/spec/unit/keypress_spec.rb +0 -58
  42. data/spec/unit/mask_spec.rb +0 -132
  43. data/spec/unit/multi_select_spec.rb +0 -319
  44. data/spec/unit/multiline_spec.rb +0 -77
  45. data/spec/unit/new_spec.rb +0 -20
  46. data/spec/unit/ok_spec.rb +0 -10
  47. data/spec/unit/paginator_spec.rb +0 -63
  48. data/spec/unit/question/checks_spec.rb +0 -97
  49. data/spec/unit/question/default_spec.rb +0 -31
  50. data/spec/unit/question/echo_spec.rb +0 -38
  51. data/spec/unit/question/in_spec.rb +0 -115
  52. data/spec/unit/question/initialize_spec.rb +0 -12
  53. data/spec/unit/question/modifier/apply_to_spec.rb +0 -24
  54. data/spec/unit/question/modifier/letter_case_spec.rb +0 -41
  55. data/spec/unit/question/modifier/whitespace_spec.rb +0 -51
  56. data/spec/unit/question/modify_spec.rb +0 -41
  57. data/spec/unit/question/required_spec.rb +0 -92
  58. data/spec/unit/question/validate_spec.rb +0 -115
  59. data/spec/unit/question/validation/call_spec.rb +0 -31
  60. data/spec/unit/question/validation/coerce_spec.rb +0 -30
  61. data/spec/unit/reader/history_spec.rb +0 -172
  62. data/spec/unit/reader/key_event_spec.rb +0 -86
  63. data/spec/unit/reader/line_spec.rb +0 -110
  64. data/spec/unit/reader/publish_keypress_event_spec.rb +0 -94
  65. data/spec/unit/reader/read_keypress_spec.rb +0 -80
  66. data/spec/unit/reader/read_line_spec.rb +0 -68
  67. data/spec/unit/reader/read_multiline_spec.rb +0 -60
  68. data/spec/unit/result_spec.rb +0 -40
  69. data/spec/unit/say_spec.rb +0 -67
  70. data/spec/unit/select_spec.rb +0 -359
  71. data/spec/unit/slider_spec.rb +0 -96
  72. data/spec/unit/statement/initialize_spec.rb +0 -15
  73. data/spec/unit/suggest_spec.rb +0 -28
  74. data/spec/unit/warn_spec.rb +0 -21
  75. data/spec/unit/yes_no_spec.rb +0 -235
@@ -1,92 +0,0 @@
1
- # encoding: utf-8
2
-
3
- RSpec.describe TTY::Prompt::Question, '#required' do
4
-
5
- subject(:prompt) { TTY::TestPrompt.new }
6
-
7
- it 'requires value to be present' do
8
- prompt.input << "Piotr"
9
- prompt.input.rewind
10
- prompt.ask('What is your name?') { |q| q.required(true) }
11
- expect(prompt.output.string).to eq([
12
- "What is your name? ",
13
- "\e[2K\e[1GWhat is your name? P",
14
- "\e[2K\e[1GWhat is your name? Pi",
15
- "\e[2K\e[1GWhat is your name? Pio",
16
- "\e[2K\e[1GWhat is your name? Piot",
17
- "\e[2K\e[1GWhat is your name? Piotr",
18
- "\e[1A\e[2K\e[1G",
19
- "What is your name? \e[32mPiotr\e[0m\n"
20
- ].join)
21
- end
22
-
23
- it 'requires value to be present with option' do
24
- prompt.input << " \nPiotr"
25
- prompt.input.rewind
26
- prompt.ask('What is your name?', required: true)
27
- expect(prompt.output.string).to eq([
28
- "What is your name? ",
29
- "\e[2K\e[1GWhat is your name? ",
30
- "\e[2K\e[1GWhat is your name? ",
31
- "\e[2K\e[1GWhat is your name? \n",
32
- "\e[31m>>\e[0m Value must be provided\e[1A",
33
- "\e[2K\e[1G",
34
- "What is your name? ",
35
- "\e[2K\e[1GWhat is your name? P",
36
- "\e[2K\e[1GWhat is your name? Pi",
37
- "\e[2K\e[1GWhat is your name? Pio",
38
- "\e[2K\e[1GWhat is your name? Piot",
39
- "\e[2K\e[1GWhat is your name? Piotr",
40
- "\e[2K\e[1G",
41
- "\e[1A\e[2K\e[1G",
42
- "What is your name? \e[32mPiotr\e[0m\n"
43
- ].join)
44
- end
45
-
46
- it "doesn't require value to be present" do
47
- prompt.input << ''
48
- prompt.input.rewind
49
- answer = prompt.ask('What is your name?') { |q| q.required(false) }
50
- expect(answer).to be_nil
51
- end
52
-
53
- it "uses required in validation check" do
54
- prompt.input << " \nexists\ntest\n"
55
- prompt.input.rewind
56
- answer = prompt.ask('File name?') do |q|
57
- q.required(true)
58
- q.validate { |v| !(v =~ /exists/) }
59
- q.messages[:required?] = 'File name must not be empty!'
60
- q.messages[:valid?] = 'File already exists!'
61
- end
62
- expect(answer).to eq('test')
63
- expect(prompt.output.string).to eq([
64
- "File name? ",
65
- "\e[2K\e[1GFile name? ",
66
- "\e[2K\e[1GFile name? ",
67
- "\e[2K\e[1GFile name? \n",
68
- "\e[31m>>\e[0m File name must not be empty!",
69
- "\e[1A\e[2K\e[1G",
70
- "File name? ",
71
- "\e[2K\e[1GFile name? e",
72
- "\e[2K\e[1GFile name? ex",
73
- "\e[2K\e[1GFile name? exi",
74
- "\e[2K\e[1GFile name? exis",
75
- "\e[2K\e[1GFile name? exist",
76
- "\e[2K\e[1GFile name? exists",
77
- "\e[2K\e[1GFile name? exists\n",
78
- "\e[31m>>\e[0m File already exists!",
79
- "\e[1A\e[2K\e[1G",
80
- "File name? ",
81
- "\e[2K\e[1GFile name? t",
82
- "\e[2K\e[1GFile name? te",
83
- "\e[2K\e[1GFile name? tes",
84
- "\e[2K\e[1GFile name? test",
85
- "\e[2K\e[1GFile name? test\n",
86
- "\e[2K\e[1G",
87
- "\e[1A\e[2K\e[1G",
88
- "File name? \e[32mtest\e[0m\n",
89
- ].join)
90
- expect(answer).to eq('test')
91
- end
92
- end
@@ -1,115 +0,0 @@
1
- # encoding: utf-8
2
-
3
- RSpec.describe TTY::Prompt::Question, '#validate' do
4
-
5
- subject(:prompt) { TTY::TestPrompt.new }
6
-
7
- it 'validates input with regex' do
8
- prompt.input << 'p.m'
9
- prompt.input.rewind
10
-
11
- answer = prompt.ask('What is your username?') do |q|
12
- q.validate(/^[^\.]+\.[^\.]+/)
13
- end
14
-
15
- expect(answer).to eq('p.m')
16
- expect(prompt.output.string).to eq([
17
- "What is your username? ",
18
- "\e[2K\e[1GWhat is your username? p",
19
- "\e[2K\e[1GWhat is your username? p.",
20
- "\e[2K\e[1GWhat is your username? p.m",
21
- "\e[1A\e[2K\e[1G",
22
- "What is your username? \e[32mp.m\e[0m\n"
23
- ].join)
24
- end
25
-
26
- it 'validates input with proc' do
27
- prompt.input << 'piotr.murach'
28
- prompt.input.rewind
29
-
30
- answer = prompt.ask('What is your username?') do |q|
31
- q.validate { |input| input =~ /^[^\.]+\.[^\.]+/ }
32
- end
33
-
34
- expect(answer).to eq('piotr.murach')
35
- end
36
-
37
- it 'understands custom validation like :email' do
38
- prompt.input << 'piotr@example.com'
39
- prompt.input.rewind
40
-
41
- answer = prompt.ask('What is your email?') do |q|
42
- q.validate :email
43
- end
44
-
45
- expect(answer).to eq('piotr@example.com')
46
- end
47
-
48
- it "provides default error message for wrong input" do
49
- prompt.input << "wrong\np@m.com\n"
50
- prompt.input.rewind
51
-
52
- answer = prompt.ask('What is your email?') do |q|
53
- q.validate :email
54
- end
55
-
56
- expect(answer).to eq('p@m.com')
57
- expect(prompt.output.string).to eq([
58
- "What is your email? ",
59
- "\e[2K\e[1GWhat is your email? w",
60
- "\e[2K\e[1GWhat is your email? wr",
61
- "\e[2K\e[1GWhat is your email? wro",
62
- "\e[2K\e[1GWhat is your email? wron",
63
- "\e[2K\e[1GWhat is your email? wrong",
64
- "\e[2K\e[1GWhat is your email? wrong\n",
65
- "\e[31m>>\e[0m Your answer is invalid (must match :email)\e[1A",
66
- "\e[2K\e[1G",
67
- "What is your email? ",
68
- "\e[2K\e[1GWhat is your email? p",
69
- "\e[2K\e[1GWhat is your email? p@",
70
- "\e[2K\e[1GWhat is your email? p@m",
71
- "\e[2K\e[1GWhat is your email? p@m.",
72
- "\e[2K\e[1GWhat is your email? p@m.c",
73
- "\e[2K\e[1GWhat is your email? p@m.co",
74
- "\e[2K\e[1GWhat is your email? p@m.com",
75
- "\e[2K\e[1GWhat is your email? p@m.com\n",
76
- "\e[2K\e[1G",
77
- "\e[1A\e[2K\e[1G",
78
- "What is your email? \e[32mp@m.com\e[0m\n"
79
- ].join)
80
- end
81
-
82
- it "provides custom error message for wrong input" do
83
- prompt.input << "wrong\np@m.com"
84
- prompt.input.rewind
85
-
86
- answer = prompt.ask('What is your email?') do |q|
87
- q.validate :email
88
- q.messages[:valid?] = 'Not an email!'
89
- end
90
-
91
- expect(answer).to eq('p@m.com')
92
- expect(prompt.output.string).to eq([
93
- "What is your email? ",
94
- "\e[2K\e[1GWhat is your email? w",
95
- "\e[2K\e[1GWhat is your email? wr",
96
- "\e[2K\e[1GWhat is your email? wro",
97
- "\e[2K\e[1GWhat is your email? wron",
98
- "\e[2K\e[1GWhat is your email? wrong",
99
- "\e[2K\e[1GWhat is your email? wrong\n",
100
- "\e[31m>>\e[0m Not an email!\e[1A",
101
- "\e[2K\e[1G",
102
- "What is your email? ",
103
- "\e[2K\e[1GWhat is your email? p",
104
- "\e[2K\e[1GWhat is your email? p@",
105
- "\e[2K\e[1GWhat is your email? p@m",
106
- "\e[2K\e[1GWhat is your email? p@m.",
107
- "\e[2K\e[1GWhat is your email? p@m.c",
108
- "\e[2K\e[1GWhat is your email? p@m.co",
109
- "\e[2K\e[1GWhat is your email? p@m.com",
110
- "\e[2K\e[1G",
111
- "\e[1A\e[2K\e[1G",
112
- "What is your email? \e[32mp@m.com\e[0m\n"
113
- ].join)
114
- end
115
- end
@@ -1,31 +0,0 @@
1
- # encoding: utf-8
2
-
3
- RSpec.describe TTY::Prompt::Question::Validation, '#call' do
4
- let(:pattern) { /^[^\.]+\.[^\.]+/ }
5
-
6
- it "validates nil input" do
7
- validation = described_class.new(pattern)
8
- expect(validation.(nil)).to eq(false)
9
- end
10
-
11
- it "validates successfully when the value matches pattern" do
12
- validation = described_class.new(pattern)
13
- expect(validation.('piotr.murach')).to eq(true)
14
- end
15
-
16
- it "validates with a proc" do
17
- pat = proc { |input| !pattern.match(input).nil? }
18
- validation = described_class.new(pat)
19
- expect(validation.call('piotr.murach')).to eq(true)
20
- end
21
-
22
- it "validates with custom name" do
23
- validation = described_class.new(:email)
24
- expect(validation.call('piotr@example.com')).to eq(true)
25
- end
26
-
27
- it "fails validation when not maching pattern" do
28
- validation = described_class.new(pattern)
29
- expect(validation.('piotrmurach')).to eq(false)
30
- end
31
- end
@@ -1,30 +0,0 @@
1
- # encoding: utf-8
2
-
3
- RSpec.describe TTY::Prompt::Question::Validation, '#coerce' do
4
- let(:instance) { described_class.new }
5
-
6
- it "coerces lambda into proc" do
7
- pattern = lambda { "^[^\.]+\.[^\.]+" }
8
- validation = described_class.new(pattern)
9
- expect(validation.pattern).to be_a(Proc)
10
- end
11
-
12
- it "doesn't coerce symbols" do
13
- pattern = :email
14
- validation =described_class.new(pattern)
15
- expect(validation.pattern).to eq(:email)
16
- end
17
-
18
- it "coerces into regex" do
19
- pattern = /^[^\.]+\.[^\.]+/
20
- validation = described_class.new(pattern)
21
- expect(validation.pattern).to be_a(Regexp)
22
- end
23
-
24
- it "fails to coerce pattern into validation" do
25
- pattern = Object.new
26
- expect {
27
- described_class.new(pattern)
28
- }.to raise_error(TTY::Prompt::ValidationCoercion)
29
- end
30
- end
@@ -1,172 +0,0 @@
1
- # encoding: utf-8
2
-
3
- RSpec.describe TTY::Prompt::Reader::History do
4
- it "has no lines" do
5
- history = described_class.new
6
- expect(history.size).to eq(0)
7
- end
8
-
9
- it "doesn't navigate through empty buffer" do
10
- history = described_class.new
11
- expect(history.next?).to eq(false)
12
- expect(history.previous?).to eq(false)
13
- end
14
-
15
- it "allows to cycle through non-empty buffer" do
16
- history = described_class.new(3, {cycle: true})
17
- history << "line"
18
- expect(history.next?).to eq(true)
19
- expect(history.previous?).to eq(true)
20
- end
21
-
22
- it "defaults maximum size" do
23
- history = described_class.new
24
- expect(history.max_size).to eq(512)
25
- end
26
-
27
- it "presents string representation" do
28
- history = described_class.new
29
- expect(history.to_s).to eq("[]")
30
- end
31
-
32
- it "adds items to history without overflowing" do
33
- history = described_class.new(3)
34
- history << "line #1"
35
- history << "line #2"
36
- history << "line #3"
37
- history << "line #4"
38
-
39
- expect(history.to_a).to eq(["line #2", "line #3", "line #4"])
40
- expect(history.index).to eq(2)
41
- end
42
-
43
- it "excludes items" do
44
- exclude = proc { |line| /line #[23]/.match(line) }
45
- history = described_class.new(3, exclude: exclude)
46
- history << "line #1"
47
- history << "line #2"
48
- history << "line #3"
49
-
50
- expect(history.to_a).to eq(["line #1"])
51
- expect(history.index).to eq(0)
52
- end
53
-
54
- it "allows duplicates" do
55
- history = described_class.new(3)
56
- history << "line #1"
57
- history << "line #1"
58
- history << "line #1"
59
-
60
- expect(history.to_a).to eq(["line #1", "line #1", "line #1"])
61
- end
62
-
63
- it "prevents duplicates" do
64
- history = described_class.new(3, duplicates: false)
65
- history << "line #1"
66
- history << "line #1"
67
- history << "line #1"
68
-
69
- expect(history.to_a).to eq(["line #1"])
70
- end
71
-
72
- it "navigates through history buffer without cycling" do
73
- history = described_class.new(3)
74
- history << "line #1"
75
- history << "line #2"
76
- history << "line #3"
77
-
78
- expect(history.index).to eq(2)
79
- history.previous
80
- history.previous
81
- expect(history.index).to eq(0)
82
- history.previous
83
- expect(history.index).to eq(0)
84
- history.next
85
- history.next
86
- expect(history.index).to eq(2)
87
- history.next
88
- expect(history.next?).to eq(false)
89
- expect(history.index).to eq(2)
90
- end
91
-
92
- it "navigates through history buffer with cycling" do
93
- history = described_class.new(3, cycle: true)
94
- history << "line #1"
95
- history << "line #2"
96
- history << "line #3"
97
-
98
- expect(history.index).to eq(2)
99
- history.previous
100
- history.previous
101
- expect(history.index).to eq(0)
102
- history.previous
103
- expect(history.index).to eq(2)
104
- expect(history.next?).to eq(true)
105
- history.next
106
- history.next
107
- expect(history.index).to eq(1)
108
- history.next
109
- expect(history.index).to eq(2)
110
- end
111
-
112
- it "checks if navigation is possible" do
113
- history = described_class.new(3)
114
- expect(history.previous?).to eq(false)
115
- expect(history.next?).to eq(false)
116
-
117
- history << "line #1"
118
- history << "line #2"
119
- expect(history.previous?).to eq(true)
120
- expect(history.next?).to eq(false)
121
-
122
- history.previous
123
- expect(history.previous?).to eq(true)
124
- expect(history.next?).to eq(true)
125
-
126
- history.previous
127
- expect(history.previous?).to eq(true)
128
- expect(history.next?).to eq(true)
129
- end
130
-
131
- it "gets line based on index" do
132
- history = described_class.new(3, cycle: true)
133
- history << "line #1"
134
- history << "line #2"
135
- history << "line #3"
136
-
137
- expect(history[-1]).to eq('line #3')
138
- expect(history[1]).to eq('line #2')
139
- expect {
140
- history[11]
141
- }.to raise_error(IndexError, 'invalid index')
142
- end
143
-
144
- it "retrieves current line" do
145
- history = described_class.new(3, cycle: true)
146
- expect(history.get).to eq(nil)
147
-
148
- history << "line #1"
149
- history << "line #2"
150
- history << "line #3"
151
-
152
- expect(history.get).to eq("line #3")
153
- history.previous
154
- history.previous
155
- expect(history.get).to eq("line #1")
156
- history.next
157
- expect(history.get).to eq("line #2")
158
- end
159
-
160
- it "clears all lines" do
161
- history = described_class.new(3)
162
-
163
- history << "line #1"
164
- history << "line #2"
165
- history << "line #3"
166
-
167
- expect(history.size).to eq(3)
168
- history.clear
169
- expect(history.size).to eq(0)
170
- expect(history.index).to eq(0)
171
- end
172
- end
@@ -1,86 +0,0 @@
1
- # encoding: utf-8
2
-
3
- require 'shellwords'
4
- require 'tty/prompt/reader/codes'
5
-
6
- RSpec.describe TTY::Prompt::Reader::KeyEvent, '#from' do
7
- let(:keys) { TTY::Prompt::Reader::Codes.keys }
8
-
9
- it "parses backspace" do
10
- event = described_class.from(keys, "\x7f")
11
- expect(event.key.name).to eq(:backspace)
12
- expect(event.value).to eq("\x7f")
13
- end
14
-
15
- it "parses lowercase char" do
16
- event = described_class.from(keys, 'a')
17
- expect(event.key.name).to eq(:alpha)
18
- expect(event.value).to eq('a')
19
- end
20
-
21
- it "parses uppercase char" do
22
- event = described_class.from(keys, 'A')
23
- expect(event.key.name).to eq(:alpha)
24
- expect(event.value).to eq('A')
25
- end
26
-
27
- it "parses ctrl-a to ctrl-z inputs" do
28
- (1..26).zip('a'..'z').each do |code, char|
29
- next if ['i', 'j', 'm'].include?(char)
30
- event = described_class.from(keys, code.chr)
31
- expect(event.key.name).to eq(:"ctrl_#{char}")
32
- expect(event.value).to eq(code.chr)
33
- end
34
- end
35
-
36
- # F1-F12 keys
37
- {
38
- f1: ["\eOP", "\e[11~"],
39
- f2: ["\eOQ", "\e[12~"],
40
- f3: ["\eOR", "\e[13~"],
41
- f4: ["\eOS", "\e[14~"],
42
- f5: [ "\e[15~"],
43
- f6: [ "\e[17~"],
44
- f7: [ "\e[18~"],
45
- f8: [ "\e[19~"],
46
- f9: [ "\e[20~"],
47
- f10: [ "\e[21~"],
48
- f11: [ "\e[23~"],
49
- f12: [ "\e[24~"]
50
- }.each do |name, codes|
51
- codes.each do |code|
52
- it "parses #{Shellwords.escape(code)} as #{name} key" do
53
- event = described_class.from(keys, code)
54
- expect(event.key.name).to eq(name)
55
- expect(event.key.meta).to eq(false)
56
- expect(event.key.ctrl).to eq(false)
57
- expect(event.key.shift).to eq(false)
58
- end
59
- end
60
- end
61
-
62
- # arrow keys & text editing
63
- {
64
- up: ["\e[A"],
65
- down: ["\e[B"],
66
- right: ["\e[C"],
67
- left: ["\e[D"],
68
- clear: ["\e[E"],
69
- home: ["\e[1~"],
70
- insert: ["\e[2~"],
71
- delete: ["\e[3~"],
72
- end: ["\e[4~"],
73
- page_up: ["\e[5~"],
74
- page_down: ["\e[6~"]
75
- }.each do |name, codes|
76
- codes.each do |code|
77
- it "parses #{Shellwords.escape(code)} as #{name} key" do
78
- event = described_class.from(keys, code)
79
- expect(event.key.name).to eq(name)
80
- expect(event.key.meta).to eq(false)
81
- expect(event.key.ctrl).to eq(false)
82
- expect(event.key.shift).to eq(false)
83
- end
84
- end
85
- end
86
- end