tty-prompt 0.19.0 → 0.23.1

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.
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