tty-prompt 0.19.0 → 0.23.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (135) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +81 -0
  3. data/LICENSE.txt +1 -1
  4. data/README.md +485 -233
  5. data/lib/tty-prompt.rb +1 -2
  6. data/lib/tty/prompt.rb +159 -147
  7. data/lib/tty/prompt/answers_collector.rb +5 -5
  8. data/lib/tty/prompt/block_paginator.rb +1 -1
  9. data/lib/tty/prompt/choice.rb +31 -13
  10. data/lib/tty/prompt/choices.rb +30 -12
  11. data/lib/tty/prompt/confirm_question.rb +42 -16
  12. data/lib/tty/prompt/const.rb +17 -0
  13. data/lib/tty/prompt/converter_dsl.rb +6 -7
  14. data/lib/tty/prompt/converter_registry.rb +31 -26
  15. data/lib/tty/prompt/converters.rb +139 -32
  16. data/lib/tty/prompt/enum_list.rb +58 -25
  17. data/lib/tty/prompt/errors.rb +31 -0
  18. data/lib/tty/prompt/evaluator.rb +2 -2
  19. data/lib/tty/prompt/expander.rb +27 -15
  20. data/lib/tty/prompt/keypress.rb +5 -3
  21. data/lib/tty/prompt/list.rb +101 -38
  22. data/lib/tty/prompt/mask_question.rb +9 -5
  23. data/lib/tty/prompt/multi_list.rb +108 -29
  24. data/lib/tty/prompt/multiline.rb +9 -7
  25. data/lib/tty/prompt/paginator.rb +1 -1
  26. data/lib/tty/prompt/question.rb +67 -36
  27. data/lib/tty/prompt/question/checks.rb +20 -2
  28. data/lib/tty/prompt/question/modifier.rb +4 -2
  29. data/lib/tty/prompt/question/validation.rb +3 -3
  30. data/lib/tty/prompt/selected_choices.rb +77 -0
  31. data/lib/tty/prompt/slider.rb +110 -23
  32. data/lib/tty/prompt/statement.rb +3 -3
  33. data/lib/tty/prompt/suggestion.rb +7 -6
  34. data/lib/tty/prompt/symbols.rb +58 -58
  35. data/lib/tty/prompt/test.rb +36 -0
  36. data/lib/tty/prompt/utils.rb +1 -3
  37. data/lib/tty/prompt/version.rb +1 -1
  38. metadata +27 -196
  39. data/Rakefile +0 -8
  40. data/examples/ask.rb +0 -7
  41. data/examples/ask_blank.rb +0 -9
  42. data/examples/ask_valid.rb +0 -12
  43. data/examples/collect.rb +0 -21
  44. data/examples/echo.rb +0 -11
  45. data/examples/enum_select.rb +0 -7
  46. data/examples/enum_select_disabled.rb +0 -16
  47. data/examples/enum_select_paged.rb +0 -9
  48. data/examples/enum_select_wrapped.rb +0 -15
  49. data/examples/expand.rb +0 -29
  50. data/examples/expand_auto.rb +0 -29
  51. data/examples/in.rb +0 -9
  52. data/examples/inputs.rb +0 -10
  53. data/examples/key_events.rb +0 -15
  54. data/examples/keypress.rb +0 -9
  55. data/examples/mask.rb +0 -13
  56. data/examples/multi_select.rb +0 -8
  57. data/examples/multi_select_disabled.rb +0 -17
  58. data/examples/multi_select_disabled_paged.rb +0 -22
  59. data/examples/multi_select_paged.rb +0 -9
  60. data/examples/multi_select_wrapped.rb +0 -15
  61. data/examples/multiline.rb +0 -9
  62. data/examples/pause.rb +0 -9
  63. data/examples/select.rb +0 -24
  64. data/examples/select_disabled.rb +0 -18
  65. data/examples/select_disabled_paged.rb +0 -22
  66. data/examples/select_enum.rb +0 -8
  67. data/examples/select_filtered.rb +0 -11
  68. data/examples/select_paginated.rb +0 -11
  69. data/examples/select_wrapped.rb +0 -15
  70. data/examples/slider.rb +0 -6
  71. data/examples/validation.rb +0 -9
  72. data/examples/yes_no.rb +0 -7
  73. data/lib/tty/prompt/messages.rb +0 -49
  74. data/lib/tty/test_prompt.rb +0 -20
  75. data/spec/spec_helper.rb +0 -61
  76. data/spec/unit/ask_spec.rb +0 -173
  77. data/spec/unit/block_paginator_spec.rb +0 -84
  78. data/spec/unit/choice/eql_spec.rb +0 -22
  79. data/spec/unit/choice/from_spec.rb +0 -112
  80. data/spec/unit/choices/add_spec.rb +0 -12
  81. data/spec/unit/choices/each_spec.rb +0 -13
  82. data/spec/unit/choices/find_by_spec.rb +0 -10
  83. data/spec/unit/choices/new_spec.rb +0 -10
  84. data/spec/unit/choices/pluck_spec.rb +0 -9
  85. data/spec/unit/collect_spec.rb +0 -96
  86. data/spec/unit/converters/convert_bool_spec.rb +0 -58
  87. data/spec/unit/converters/convert_char_spec.rb +0 -11
  88. data/spec/unit/converters/convert_custom_spec.rb +0 -14
  89. data/spec/unit/converters/convert_date_spec.rb +0 -34
  90. data/spec/unit/converters/convert_file_spec.rb +0 -18
  91. data/spec/unit/converters/convert_number_spec.rb +0 -39
  92. data/spec/unit/converters/convert_path_spec.rb +0 -15
  93. data/spec/unit/converters/convert_range_spec.rb +0 -22
  94. data/spec/unit/converters/convert_regex_spec.rb +0 -12
  95. data/spec/unit/converters/convert_string_spec.rb +0 -21
  96. data/spec/unit/converters/on_error_spec.rb +0 -9
  97. data/spec/unit/distance/distance_spec.rb +0 -73
  98. data/spec/unit/enum_select_spec.rb +0 -518
  99. data/spec/unit/error_spec.rb +0 -20
  100. data/spec/unit/evaluator_spec.rb +0 -67
  101. data/spec/unit/expand_spec.rb +0 -290
  102. data/spec/unit/keypress_spec.rb +0 -66
  103. data/spec/unit/mask_spec.rb +0 -140
  104. data/spec/unit/multi_select_spec.rb +0 -741
  105. data/spec/unit/multiline_spec.rb +0 -77
  106. data/spec/unit/new_spec.rb +0 -20
  107. data/spec/unit/ok_spec.rb +0 -10
  108. data/spec/unit/paginator_spec.rb +0 -92
  109. data/spec/unit/question/checks_spec.rb +0 -97
  110. data/spec/unit/question/default_spec.rb +0 -31
  111. data/spec/unit/question/echo_spec.rb +0 -38
  112. data/spec/unit/question/in_spec.rb +0 -115
  113. data/spec/unit/question/initialize_spec.rb +0 -12
  114. data/spec/unit/question/modifier/apply_to_spec.rb +0 -24
  115. data/spec/unit/question/modifier/letter_case_spec.rb +0 -41
  116. data/spec/unit/question/modifier/whitespace_spec.rb +0 -51
  117. data/spec/unit/question/modify_spec.rb +0 -41
  118. data/spec/unit/question/required_spec.rb +0 -92
  119. data/spec/unit/question/validate_spec.rb +0 -115
  120. data/spec/unit/question/validation/call_spec.rb +0 -31
  121. data/spec/unit/question/validation/coerce_spec.rb +0 -30
  122. data/spec/unit/result_spec.rb +0 -40
  123. data/spec/unit/say_spec.rb +0 -67
  124. data/spec/unit/select_spec.rb +0 -942
  125. data/spec/unit/slider_spec.rb +0 -142
  126. data/spec/unit/statement/initialize_spec.rb +0 -15
  127. data/spec/unit/subscribe_spec.rb +0 -22
  128. data/spec/unit/suggest_spec.rb +0 -28
  129. data/spec/unit/timer_spec.rb +0 -29
  130. data/spec/unit/warn_spec.rb +0 -21
  131. data/spec/unit/yes_no_spec.rb +0 -251
  132. data/tasks/console.rake +0 -11
  133. data/tasks/coverage.rake +0 -11
  134. data/tasks/spec.rake +0 -29
  135. data/tty-prompt.gemspec +0 -31
@@ -1,22 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- RSpec.describe TTY::Prompt::Choice, '#==' do
4
- it "is true with the same name and value attributes" do
5
- expect(described_class.new(:large, 1)).
6
- to eq(described_class.new(:large, 1))
7
- end
8
-
9
- it "is false with different name attribute" do
10
- expect(described_class.new(:large, 1)).
11
- not_to eq(described_class.new(:medium, 1))
12
- end
13
-
14
- it "is false with different value attribute" do
15
- expect(described_class.new(:large, 1)).
16
- not_to eq(described_class.new(:large, 2))
17
- end
18
-
19
- it "is false with non-choice object" do
20
- expect(described_class.new(:large, 1)).not_to eq(:other)
21
- end
22
- end
@@ -1,112 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- RSpec.describe TTY::Prompt::Choice, '#from' do
4
- it "skips Choice instance" do
5
- choice = described_class.new(:large, 1)
6
-
7
- expect(described_class.from(choice)).to eq(choice)
8
- end
9
-
10
- it "creates choice from string" do
11
- expected_choice = described_class.new('large', 'large')
12
- choice = described_class.from('large')
13
-
14
- expect(choice).to eq(expected_choice)
15
- expect(choice.name).to eq('large')
16
- expect(choice.value).to eq('large')
17
- end
18
-
19
- it "creates choice from array with one element" do
20
- expected_choice = described_class.new('large', 'large')
21
- choice = described_class.from([:large])
22
-
23
- expect(choice).to eq(expected_choice)
24
- expect(choice.name).to eq('large')
25
- expect(choice.value).to eq('large')
26
- end
27
-
28
- it "creates choice from array with more than one element" do
29
- expected_choice = described_class.new('large', 1)
30
- choice = described_class.from([:large, 1])
31
-
32
- expect(choice).to eq(expected_choice)
33
- expect(choice.name).to eq('large')
34
- expect(choice.value).to eq(1)
35
- end
36
-
37
- it "creates choice from hash value" do
38
- expected_choice = described_class.new('large', 1)
39
- choice = described_class.from({large: 1})
40
-
41
- expect(choice).to eq(expected_choice)
42
- expect(choice.name).to eq('large')
43
- expect(choice.value).to eq(1)
44
- end
45
-
46
- it "creats choice from array with key value pair" do
47
- expected_choice = described_class.new('large', 1)
48
- choice = described_class.from([{'large' => 1}])
49
-
50
- expect(choice).to eq(expected_choice)
51
- expect(choice.name).to eq('large')
52
- expect(choice.value).to eq(1)
53
- end
54
-
55
- it "creats choice from array with hash elements" do
56
- expected_choice = described_class.new('large', 1)
57
- choice = described_class.from([{name: 'large', value: 1}])
58
-
59
- expect(choice).to eq(expected_choice)
60
- expect(choice.name).to eq('large')
61
- expect(choice.value).to eq(1)
62
- end
63
-
64
- it "creats choice from array with hash elements without value" do
65
- expected_choice = described_class.new('large', 'large')
66
- choice = described_class.from([{name: 'large'}])
67
-
68
- expect(choice).to eq(expected_choice)
69
- expect(choice.name).to eq('large')
70
- expect(choice.value).to eq('large')
71
- end
72
-
73
- it "creates choice from hash with key property" do
74
- default = {key: 'h', name: 'Help', value: :help}
75
- expected_choice = described_class.new('Help', :help, key: 'h')
76
- choice = described_class.from(default)
77
-
78
- expect(choice).to eq(expected_choice)
79
- expect(choice.name).to eq('Help')
80
- expect(choice.value).to eq(:help)
81
- expect(choice.disabled?).to eq(false)
82
- end
83
-
84
- it "creates disabled choice" do
85
- expected_choice = described_class.new('Disabled', :none, disabled: true)
86
- choice = described_class.from({
87
- name: 'Disabled',
88
- value: :none,
89
- disabled: 'unavailable'})
90
-
91
- expect(choice).to eq(expected_choice)
92
- expect(choice.name).to eq('Disabled')
93
- expect(choice.value).to eq(:none)
94
- expect(choice.disabled?).to eq(true)
95
- end
96
-
97
- it "creates choice from an arbitrary object that responds to to_s call" do
98
- stub_const("Size", Class.new do
99
- def to_s
100
- 'large'
101
- end
102
- end)
103
- size = Size.new
104
- expected_choice = described_class.new(size, size)
105
- choice = described_class.from(size)
106
-
107
- expect(choice).to eq(expected_choice)
108
- expect(choice.name).to eq(size)
109
- expect(choice.value).to eq(size)
110
- expect(choice.disabled?).to eq(false)
111
- end
112
- end
@@ -1,12 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- RSpec.describe TTY::Prompt::Choices, '#<<' do
4
- it "adds choice to collection" do
5
- choices = described_class.new
6
- expect(choices).to be_empty
7
- choice = TTY::Prompt::Choice.from([:label, 1])
8
- choices << [:label, 1]
9
- expect(choices.size).to eq(1)
10
- expect(choices.to_ary).to eq([choice])
11
- end
12
- end
@@ -1,13 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- RSpec.describe TTY::Prompt::Choices, '.each' do
4
- it "iterates over collection" do
5
- choices = described_class[:large, :medium, :small]
6
- actual = []
7
- choices.each do |choice|
8
- actual << choice.name
9
- end
10
- expect(actual).to eq([:large, :medium, :small])
11
- expect(choices.each).to be_kind_of(Enumerator)
12
- end
13
- end
@@ -1,10 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- RSpec.describe TTY::Prompt::Choices, '#find_by' do
4
- it "finds a matching choice by key name" do
5
- collection = [{name: 'large'},{name: 'medium'},{name: 'small'}]
6
- choice = TTY::Prompt::Choice.from(name: 'small')
7
- choices = described_class[*collection]
8
- expect(choices.find_by(:name, 'small')).to eq(choice)
9
- end
10
- end
@@ -1,10 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- RSpec.describe TTY::Prompt::Choices, '.new' do
4
- it "creates choices collection" do
5
- choice_1 = TTY::Prompt::Choice.from(:label1)
6
- choice_2 = TTY::Prompt::Choice.from(:label2)
7
- collection = described_class[:label1, :label2]
8
- expect(collection.choices).to eq([choice_1, choice_2])
9
- end
10
- end
@@ -1,9 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- RSpec.describe TTY::Prompt::Choices, '#pluck' do
4
- it "plucks choice by key name" do
5
- collection = [{name: 'large'},{name: 'medium'},{name: 'small'}]
6
- choices = described_class[*collection]
7
- expect(choices.pluck(:name)).to eq(['large', 'medium', 'small'])
8
- end
9
- end
@@ -1,96 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- RSpec.describe TTY::Prompt, '#collect' do
4
-
5
- subject(:prompt) { TTY::TestPrompt.new }
6
-
7
- def collect(&block)
8
- prompt = subject
9
- count = 0
10
-
11
- result = prompt.collect do
12
- while prompt.yes?("continue?")
13
- instance_eval(&block)
14
- count += 1
15
- end
16
- end
17
-
18
- result[:count] = count
19
- result
20
- end
21
-
22
- context "when receiving multiple answers" do
23
- let(:colors) { %w(red blue yellow) }
24
-
25
- before do
26
- subject.input << "y\r" + colors.join("\ry\r") + "\rn\r"
27
- subject.input.rewind
28
- end
29
-
30
- it "collects as a list if values method used in chain" do
31
- result = collect { key(:colors).values.ask("color:") }
32
- expect(result[:count]).to eq(3)
33
- expect(result[:colors]).to eq(colors)
34
- end
35
-
36
- it "collects as a list if values method used in chain with block" do
37
- result = collect do
38
- key(:colors).values { key(:name).ask("color:") }
39
- end
40
- expect(result[:count]).to eq(3)
41
- expect(result[:colors]).to eq(colors.map { |c| { name: c } })
42
- end
43
-
44
- context "with multiple keys" do
45
- let(:colors) { ["red\rblue", "yellow\rgreen"] }
46
- let(:expected_pairs) do
47
- colors.map { |s| Hash[%i(hot cold).zip(s.split("\r"))] }
48
- end
49
-
50
- it "collects into the appropriate keys" do
51
- result = collect do
52
- key(:pairs).values do
53
- key(:hot).ask("color:")
54
- key(:cold).ask("color:")
55
- end
56
- end
57
-
58
- expect(result[:count]).to eq(2)
59
- expect(result[:pairs]).to eq(expected_pairs)
60
- end
61
- end
62
-
63
- it "overrides a non-array key on multiple answers" do
64
- result = collect { key(:colors).ask("color:") }
65
- expect(result[:colors]).to eq(colors.last)
66
- expect(result[:count]).to eq(3)
67
- end
68
- end
69
-
70
- it "collects more than one answer" do
71
- prompt.input << "Piotr\r30\rStreet\rCity\r123\r"
72
- prompt.input.rewind
73
-
74
- result = prompt.collect do
75
- key(:name).ask('Name?')
76
-
77
- key(:age).ask('Age?', convert: :int)
78
-
79
- key(:address) do
80
- key(:street).ask('Street?', required: true)
81
- key(:city).ask('City?')
82
- key(:zip).ask('Zip?', validate: /\A\d{3}\Z/)
83
- end
84
- end
85
-
86
- expect(result).to include({
87
- name: 'Piotr',
88
- age: 30,
89
- address: {
90
- street: 'Street',
91
- city: 'City',
92
- zip: '123'
93
- }
94
- })
95
- end
96
- end
@@ -1,58 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- RSpec.describe TTY::Prompt::Question, 'convert bool' do
4
-
5
- subject(:prompt) { TTY::TestPrompt.new}
6
-
7
- it 'fails to convert boolean' do
8
- prompt.input << 'invalid'
9
- prompt.input.rewind
10
- expect {
11
- prompt.ask("Do you read books?", convert: :bool)
12
- }.to raise_error(TTY::Prompt::ConversionError)
13
- end
14
-
15
- it "handles default values" do
16
- prompt.input << "\n"
17
- prompt.input.rewind
18
- response = prompt.ask('Do you read books?', convert: :bool, default: true)
19
- expect(response).to eql(true)
20
- expect(prompt.output.string).to eq([
21
- "Do you read books? \e[90m(true)\e[0m ",
22
- "\e[2K\e[1GDo you read books? \e[90m(true)\e[0m \n",
23
- "\e[1A\e[2K\e[1G",
24
- "Do you read books? \e[32mtrue\e[0m\n"
25
- ].join)
26
- end
27
-
28
- it "handles default values" do
29
- prompt.input << "\n"
30
- prompt.input.rewind
31
- response = prompt.ask("Do you read books?") { |q|
32
- q.default true
33
- q.convert :bool
34
- }
35
- expect(response).to eq(true)
36
- end
37
-
38
- it 'converts negative boolean' do
39
- prompt.input << 'No'
40
- prompt.input.rewind
41
- response = prompt.ask('Do you read books?', convert: :bool)
42
- expect(response).to eq(false)
43
- end
44
-
45
- it 'converts positive boolean' do
46
- prompt.input << 'Yes'
47
- prompt.input.rewind
48
- response = prompt.ask("Do you read books?", convert: :bool)
49
- expect(response).to eq(true)
50
- end
51
-
52
- it 'converts single positive boolean' do
53
- prompt.input << 'y'
54
- prompt.input.rewind
55
- response = prompt.ask('Do you read books?', convert: :bool)
56
- expect(response).to eq(true)
57
- end
58
- end
@@ -1,11 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- RSpec.describe TTY::Prompt::Question, 'convert char' do
4
- it 'reads single character' do
5
- prompt = TTY::TestPrompt.new
6
- prompt.input << "abcde"
7
- prompt.input.rewind
8
- response = prompt.ask("What is your favourite letter?", convert: :char)
9
- expect(response).to eq('a')
10
- end
11
- end
@@ -1,14 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- RSpec.describe TTY::Prompt::Question, 'convert custom' do
4
-
5
- subject(:prompt) { TTY::TestPrompt.new }
6
-
7
- it 'converts response with custom conversion' do
8
- prompt.input << "one,two,three\n"
9
- prompt.input.rewind
10
- conversion = proc { |input| input.split(/,\s*/) }
11
- answer = prompt.ask('Ingredients? (comma sep list)', convert: conversion)
12
- expect(answer).to eq(['one','two','three'])
13
- end
14
- end
@@ -1,34 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- RSpec.describe TTY::Prompt::Question, 'convert date' do
4
-
5
- subject(:prompt) { TTY::TestPrompt.new}
6
-
7
- it 'fails to convert date' do
8
- prompt.input << 'invalid'
9
- prompt.input.rewind
10
- expect {
11
- prompt.ask("When were you born?", convert: :date)
12
- }.to raise_error(TTY::Prompt::ConversionError)
13
- end
14
-
15
- it 'converts date' do
16
- prompt.input << "20th April 1887"
17
- prompt.input.rewind
18
- response = prompt.ask("When were your born?", convert: :date)
19
- expect(response).to be_kind_of(Date)
20
- expect(response.day).to eq(20)
21
- expect(response.month).to eq(4)
22
- expect(response.year).to eq(1887)
23
- end
24
-
25
- it "converts datetime" do
26
- prompt.input << "20th April 1887"
27
- prompt.input.rewind
28
- response = prompt.ask("When were your born?", convert: :datetime)
29
- expect(response).to be_kind_of(DateTime)
30
- expect(response.day).to eq(20)
31
- expect(response.month).to eq(4)
32
- expect(response.year).to eq(1887)
33
- end
34
- end
@@ -1,18 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- RSpec.describe TTY::Prompt::Question, 'convert file' do
4
- it "converts to file" do
5
- ::File.write('test.txt', 'foobar')
6
-
7
- prompt = TTY::TestPrompt.new
8
- prompt.input << "test.txt"
9
- prompt.input.rewind
10
-
11
- answer = prompt.ask("Which file to open?", convert: :file)
12
-
13
- expect(::File.basename(answer)).to eq('test.txt')
14
- expect(::File.read(answer)).to eq('foobar')
15
-
16
- ::File.unlink('test.txt') unless Gem.win_platform?
17
- end
18
- end
@@ -1,39 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- RSpec.describe TTY::Prompt::Question, 'convert numbers' do
4
-
5
- subject(:prompt) { TTY::TestPrompt.new }
6
-
7
- it 'fails to convert integer' do
8
- prompt.input << 'invalid'
9
- prompt.input.rewind
10
- expect {
11
- prompt.ask("What temparture?", convert: :int)
12
- }.to raise_error(TTY::Prompt::ConversionError)
13
- end
14
-
15
- it 'converts integer' do
16
- prompt.input << 35
17
- prompt.input.rewind
18
- answer = prompt.ask("What temperature?", convert: :int)
19
- expect(answer).to be_a(Integer)
20
- expect(answer).to eq(35)
21
- end
22
-
23
- it 'fails to convert float' do
24
- prompt.input << 'invalid'
25
- prompt.input.rewind
26
- expect {
27
- prompt.ask("How tall are you?", convert: :float)
28
- }.to raise_error(TTY::Prompt::ConversionError)
29
- end
30
-
31
- it 'converts float' do
32
- number = 6.666
33
- prompt.input << number
34
- prompt.input.rewind
35
- answer = prompt.ask('How tall are you?', convert: :float)
36
- expect(answer).to be_a(Float)
37
- expect(answer).to eq(number)
38
- end
39
- end