tty-prompt 0.4.0 → 0.5.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -24,8 +24,6 @@ module TTY
24
24
 
25
25
  attr_reader :modifier
26
26
 
27
- attr_reader :prompt
28
-
29
27
  attr_reader :validation
30
28
 
31
29
  # Initialize a Question
@@ -33,6 +31,7 @@ module TTY
33
31
  # @api public
34
32
  def initialize(prompt, options = {})
35
33
  @prompt = prompt
34
+ @prefix = options.fetch(:prefix) { @prompt.prefix }
36
35
  @default = options.fetch(:default) { UndefinedSetting }
37
36
  @required = options.fetch(:required) { false }
38
37
  @echo = options.fetch(:echo) { true }
@@ -41,7 +40,8 @@ module TTY
41
40
  @validation = options.fetch(:validation) { UndefinedSetting }
42
41
  @read = options.fetch(:read) { UndefinedSetting }
43
42
  @convert = options.fetch(:convert) { UndefinedSetting }
44
- @color = options.fetch(:color) { :green }
43
+ @active_color = options.fetch(:active_color) { @prompt.active_color }
44
+ @help_color = options.fetch(:help_color) { @prompt.help_color }
45
45
  @messages = Utils.deep_copy(options.fetch(:messages) { { } })
46
46
  @done = false
47
47
  @input = nil
@@ -115,15 +115,13 @@ module TTY
115
115
  #
116
116
  # @api private
117
117
  def render_question
118
- header = "#{prompt.prefix}#{message} "
119
- if @convert == :bool && !@done
120
- header += @prompt.decorate('(Y/n)', :bright_black) + ' '
121
- elsif !echo?
118
+ header = "#{@prefix}#{message} "
119
+ if !echo?
122
120
  header
123
121
  elsif @done
124
- header += @prompt.decorate("#{@input}", @color)
125
- elsif default?
126
- header += @prompt.decorate("(#{default})", :bright_black) + ' '
122
+ header += @prompt.decorate("#{@input}", @active_color)
123
+ elsif default? && !Utils.blank?(@default)
124
+ header += @prompt.decorate("(#{default})", @help_color) + ' '
127
125
  end
128
126
  @prompt.print(header)
129
127
  @prompt.print("\n") if @done
@@ -14,13 +14,15 @@ module TTY
14
14
  # @api public
15
15
  def initialize(prompt, options = {})
16
16
  @prompt = prompt
17
- @first_render = true
18
- @done = false
19
- @color = options.fetch(:color) { :green }
17
+ @prefix = options.fetch(:prefix) { @prompt.prefix }
20
18
  @min = options.fetch(:min) { 0 }
21
19
  @max = options.fetch(:max) { 10 }
22
20
  @step = options.fetch(:step) { 1 }
23
21
  @default = options[:default]
22
+ @active_color = options.fetch(:active_color) { @prompt.active_color }
23
+ @help_color = options.fetch(:help_color) { @prompt.help_color }
24
+ @first_render = true
25
+ @done = false
24
26
 
25
27
  @prompt.subscribe(self)
26
28
  end
@@ -133,7 +135,7 @@ module TTY
133
135
  #
134
136
  # @api private
135
137
  def render_question
136
- header = "#{@prompt.prefix}#{@question} #{render_header}"
138
+ header = "#{@prefix}#{@question} #{render_header}"
137
139
  @prompt.puts(header)
138
140
  @first_render = false
139
141
  @prompt.print(render_slider) unless @done
@@ -144,9 +146,9 @@ module TTY
144
146
  # @api private
145
147
  def render_header
146
148
  if @done
147
- @prompt.decorate(render_answer.to_s, @color)
149
+ @prompt.decorate(render_answer.to_s, @active_color)
148
150
  elsif @first_render
149
- @prompt.decorate(HELP, :bright_black)
151
+ @prompt.decorate(HELP, @help_color)
150
152
  end
151
153
  end
152
154
 
@@ -159,7 +161,7 @@ module TTY
159
161
  output = ''
160
162
  output << Symbols::SLIDER_END
161
163
  output << '-' * @active
162
- output << @prompt.decorate(Symbols::SLIDER_HANDLE, @color)
164
+ output << @prompt.decorate(Symbols::SLIDER_HANDLE, @active_color)
163
165
  output << '-' * (range.size - @active - 1)
164
166
  output << Symbols::SLIDER_END
165
167
  output << " #{range[@active]}"
@@ -2,6 +2,6 @@
2
2
 
3
3
  module TTY
4
4
  class Prompt
5
- VERSION = "0.4.0"
5
+ VERSION = "0.5.0"
6
6
  end # Prompt
7
7
  end # TTY
@@ -1,6 +1,7 @@
1
1
  # encoding: utf-8
2
2
 
3
3
  RSpec.describe TTY::Prompt, '#ask' do
4
+
4
5
  subject(:prompt) { TTY::TestPrompt.new }
5
6
 
6
7
  it 'asks question' do
@@ -54,4 +55,71 @@ RSpec.describe TTY::Prompt, '#ask' do
54
55
  "What is your name? \e[32mPiotr\e[0m\n"
55
56
  ].join)
56
57
  end
58
+
59
+ it "changes question color" do
60
+ prompt.input << ''
61
+ prompt.input.rewind
62
+ options = {default: 'Piotr', help_color: :red, active_color: :cyan}
63
+ answer = prompt.ask("What is your name?", options)
64
+ expect(answer).to eq('Piotr')
65
+ expect(prompt.output.string).to eq([
66
+ "What is your name? \e[31m(Piotr)\e[0m ",
67
+ "\e[1000D\e[K\e[1A",
68
+ "\e[1000D\e[K",
69
+ "What is your name? \e[36mPiotr\e[0m\n"
70
+ ].join)
71
+ end
72
+
73
+ it "permits empty default parameter" do
74
+ prompt.input << "\r"
75
+ prompt.input.rewind
76
+
77
+ answer = prompt.ask("What is your name?", default: '')
78
+ expect(answer).to eq('')
79
+ expect(prompt.output.string).to eq([
80
+ "What is your name? ",
81
+ "\e[1000D\e[K\e[1A",
82
+ "\e[1000D\e[K",
83
+ "What is your name? \n"
84
+ ].join)
85
+ end
86
+
87
+ it "permits nil default parameter" do
88
+ prompt.input << "\r"
89
+ prompt.input.rewind
90
+
91
+ answer = prompt.ask("What is your name?", default: nil)
92
+ expect(answer).to eq(nil)
93
+ expect(prompt.output.string).to eq([
94
+ "What is your name? ",
95
+ "\e[1000D\e[K\e[1A",
96
+ "\e[1000D\e[K",
97
+ "What is your name? \n"
98
+ ].join)
99
+ end
100
+
101
+ it "overwrites global settings" do
102
+ global_settings = {prefix: "[?] ", active_color: :cyan, help_color: :red}
103
+ prompt = TTY::TestPrompt.new(global_settings)
104
+
105
+ prompt.input << "Piotr\r"
106
+ prompt.input.rewind
107
+ prompt.ask('What is your name?')
108
+
109
+ prompt.input << "Piotr\r"
110
+ prompt.input.rewind
111
+ local_settings = {prefix: ':-) ', active_color: :blue, help_color: :magenta}
112
+ prompt.ask('What is your name?', local_settings)
113
+
114
+ expect(prompt.output.string).to eq([
115
+ "[?] What is your name? ",
116
+ "\e[1000D\e[K\e[1A",
117
+ "\e[1000D\e[K",
118
+ "[?] What is your name? \e[36mPiotr\e[0m\n",
119
+ ":-) What is your name? ",
120
+ "\e[1000D\e[K\e[1A",
121
+ "\e[1000D\e[K",
122
+ ":-) What is your name? \e[34mPiotr\e[0m\n"
123
+ ].join)
124
+ end
57
125
  end
@@ -1,7 +1,7 @@
1
1
  # encoding: utf-8
2
2
 
3
3
  RSpec.describe TTY::Prompt::Choice, '#from' do
4
- it "creates choice from choice" do
4
+ it "skips Choice instance" do
5
5
  choice = described_class.new(:large, 1)
6
6
  expect(described_class.from(choice)).to eq(choice)
7
7
  end
@@ -20,4 +20,10 @@ RSpec.describe TTY::Prompt::Choice, '#from' do
20
20
  choice = described_class.new('large', 1)
21
21
  expect(described_class.from({large: 1})).to eq(choice)
22
22
  end
23
+
24
+ it "create choice from hash with key property" do
25
+ default = {key: 'h', name: 'Help', value: :help}
26
+ choice = described_class.new('Help', :help)
27
+ expect(described_class.from(default)).to eq(choice)
28
+ end
23
29
  end
@@ -0,0 +1,10 @@
1
+ # encoding: utf-8
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,9 +1,9 @@
1
1
  # encoding: utf-8
2
2
 
3
- RSpec.describe TTY::Prompt::Choices, '.pluck' do
4
- it "plucks choice from collection by name" do
5
- collection = %w(large medium small)
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
6
  choices = described_class[*collection]
7
- expect(choices.pluck('medium').name).to eq('medium')
7
+ expect(choices.pluck(:name)).to eq(['large', 'medium', 'small'])
8
8
  end
9
9
  end
@@ -0,0 +1,33 @@
1
+ # encoding: utf-8
2
+
3
+ RSpec.describe TTY::Prompt, '#collect' do
4
+
5
+ subject(:prompt) { TTY::TestPrompt.new }
6
+
7
+ it "collects more than one answer" do
8
+ prompt.input << "Piotr\r30\rStreet\rCity\r123\r"
9
+ prompt.input.rewind
10
+
11
+ result = prompt.collect do
12
+ key(:name).ask('Name?')
13
+
14
+ key(:age).ask('Age?', convert: :int)
15
+
16
+ key(:address) do
17
+ key(:street).ask('Street?', required: true)
18
+ key(:city).ask('City?')
19
+ key(:zip).ask('Zip?', validate: /\A\d{3}\Z/)
20
+ end
21
+ end
22
+
23
+ expect(result).to include({
24
+ name: 'Piotr',
25
+ age: 30,
26
+ address: {
27
+ street: 'Street',
28
+ city: 'City',
29
+ zip: '123'
30
+ }
31
+ })
32
+ end
33
+ end
@@ -18,7 +18,7 @@ RSpec.describe TTY::Prompt::Question, 'convert bool' do
18
18
  response = prompt.ask('Do you read books?', convert: :bool, default: true)
19
19
  expect(response).to eql(true)
20
20
  expect(prompt.output.string).to eq([
21
- "Do you read books? \e[90m(Y/n)\e[0m ",
21
+ "Do you read books? \e[90m(true)\e[0m ",
22
22
  "\e[1000D\e[K\e[1A",
23
23
  "\e[1000D\e[K",
24
24
  "Do you read books? \e[32mtrue\e[0m\n"
@@ -16,7 +16,8 @@ RSpec.describe TTY::Prompt do
16
16
  " 2) /usr/bin/vim.basic\n",
17
17
  " 3) /usr/bin/vim.tiny\n",
18
18
  " Choose 1-3 [1]: ",
19
- "\e[1000D\e[K\e[1A\e[1000D\e[K\e[1A\e[1000D\e[K\e[1A\e[1000D\e[K\e[1A\e[1000D\e[K\e[J",
19
+ "\e[1000D\e[K\e[1A" * 4,
20
+ "\e[1000D\e[K\e[J",
20
21
  "Select an editor? \e[32m/bin/nano\e[0m\n"
21
22
  ].join)
22
23
  end
@@ -90,4 +91,22 @@ RSpec.describe TTY::Prompt do
90
91
  "Select an editor? \e[32mvim\e[0m\n"
91
92
  ].join)
92
93
  end
94
+
95
+ it "changes colors for selection, hint and error" do
96
+ choices = %w(/bin/nano /usr/bin/vim.basic /usr/bin/vim.tiny)
97
+ prompt.input << "\n"
98
+ prompt.input.rewind
99
+ options = {active_color: :red, help_color: :blue, error_color: :green}
100
+ expect(prompt.enum_select("Select an editor?", choices, options)).to eq('/bin/nano')
101
+ expect(prompt.output.string).to eq([
102
+ "Select an editor? \n",
103
+ "\e[31m 1) /bin/nano\e[0m\n",
104
+ " 2) /usr/bin/vim.basic\n",
105
+ " 3) /usr/bin/vim.tiny\n",
106
+ " Choose 1-3 [1]: ",
107
+ "\e[1000D\e[K\e[1A" * 4,
108
+ "\e[1000D\e[K\e[J",
109
+ "Select an editor? \e[31m/bin/nano\e[0m\n"
110
+ ].join)
111
+ end
93
112
  end
@@ -0,0 +1,198 @@
1
+ # encoding: utf-8
2
+
3
+ RSpec.describe TTY::Prompt, '#expand' do
4
+
5
+ subject(:prompt) { TTY::TestPrompt.new }
6
+
7
+ let(:choices) {
8
+ [{
9
+ key: 'y',
10
+ name: 'Overwrite',
11
+ value: :yes
12
+ }, {
13
+ key: 'n',
14
+ name: 'Skip',
15
+ value: :no
16
+ }, {
17
+ key: 'a',
18
+ name: 'Overwrite all',
19
+ value: :all
20
+ }, {
21
+ key: 'd',
22
+ name: 'Show diff',
23
+ value: :diff
24
+ }, {
25
+ key: 'q',
26
+ name: 'Quit',
27
+ value: :quit
28
+ }]
29
+ }
30
+
31
+ it "expands default option" do
32
+ prompt.input << "\n"
33
+ prompt.input.rewind
34
+
35
+ result = prompt.expand('Overwrite Gemfile?', choices)
36
+ expect(result).to eq(:yes)
37
+
38
+ expect(prompt.output.string).to eq([
39
+ "Overwrite Gemfile? (enter \"h\" for help) [\e[32my\e[0m,n,a,d,q,h] ",
40
+ "\e[1000D\e[K",
41
+ "Overwrite Gemfile? \e[32mOverwrite\e[0m\n"
42
+ ].join)
43
+ end
44
+
45
+ it "changes default option" do
46
+ prompt.input << "\n"
47
+ prompt.input.rewind
48
+
49
+ result = prompt.expand('Overwrite Gemfile?', choices, default: 3)
50
+ expect(result).to eq(:all)
51
+
52
+ expect(prompt.output.string).to eq([
53
+ "Overwrite Gemfile? (enter \"h\" for help) [y,n,\e[32ma\e[0m,d,q,h] ",
54
+ "\e[1000D\e[K",
55
+ "Overwrite Gemfile? \e[32mOverwrite all\e[0m\n"
56
+ ].join)
57
+ end
58
+
59
+ it "expands chosen option with extra information" do
60
+ prompt.input << "a\n"
61
+ prompt.input.rewind
62
+
63
+ result = prompt.expand('Overwrite Gemfile?', choices)
64
+ expect(result).to eq(:all)
65
+
66
+ expect(prompt.output.string).to eq([
67
+ "Overwrite Gemfile? (enter \"h\" for help) [\e[32my\e[0m,n,a,d,q,h] ",
68
+ "\e[1000D\e[K",
69
+ "Overwrite Gemfile? (enter \"h\" for help) [y,n,\e[32ma\e[0m,d,q,h] ",
70
+ "a\n",
71
+ "\e[32m>> \e[0mOverwrite all",
72
+ "\e[F\e[55C",
73
+ "\e[1000D\e[K",
74
+ "\e[1B",
75
+ "\e[1000D\e[K",
76
+ "\e[F",
77
+ "Overwrite Gemfile? \e[32mOverwrite all\e[0m\n"
78
+ ].join)
79
+ end
80
+
81
+ it "expands help option and then defaults" do
82
+ prompt.input << "h\nd\n"
83
+ prompt.input.rewind
84
+
85
+ result = prompt.expand('Overwrite Gemfile?', choices)
86
+ expect(result).to eq(:diff)
87
+
88
+ expect(prompt.output.string).to eq([
89
+ "Overwrite Gemfile? (enter \"h\" for help) [\e[32my\e[0m,n,a,d,q,h] ",
90
+ "\e[1000D\e[K",
91
+ "Overwrite Gemfile? (enter \"h\" for help) [y,n,a,d,q,\e[32mh\e[0m] h\n",
92
+ "\e[32m>> \e[0mprint help",
93
+ "\e[F\e[55C",
94
+ "\e[1000D\e[K",
95
+ "\e[1B",
96
+ "\e[1000D\e[K",
97
+ "\e[F",
98
+ "Overwrite Gemfile? \n",
99
+ " y - Overwrite\n",
100
+ " n - Skip\n",
101
+ " a - Overwrite all\n",
102
+ " d - Show diff\n",
103
+ " q - Quit\n",
104
+ " h - print help\n",
105
+ " Choice [y]: ",
106
+ "\e[1000D\e[K\e[1A" * 7,
107
+ "\e[1000D\e[K",
108
+ "Overwrite Gemfile? \n",
109
+ " y - Overwrite\n",
110
+ " n - Skip\n",
111
+ " a - Overwrite all\n",
112
+ " \e[32md - Show diff\e[0m\n",
113
+ " q - Quit\n",
114
+ " h - print help\n",
115
+ " Choice [y]: d",
116
+ "\e[1000D\e[K\e[1A" * 7,
117
+ "\e[1000D\e[K",
118
+ "Overwrite Gemfile? \e[32mShow diff\e[0m\n",
119
+ ].join)
120
+ end
121
+
122
+ it "specifies options through DSL" do
123
+ prompt.input << "\n"
124
+ prompt.input.rewind
125
+
126
+ result = prompt.expand('Overwrite Gemfile?') do |q|
127
+ q.default 4
128
+
129
+ q.choice key: 'y', name: 'Overwrite', value: :yes
130
+ q.choice key: 'n', name: 'Skip', value: :no
131
+ q.choice key: 'a', name: 'Overwrite all', value: :all
132
+ q.choice key: 'd', name: 'Show diff', value: :diff
133
+ q.choice key: 'q', name: 'Quit', value: :quit
134
+ end
135
+
136
+ expect(result).to eq(:diff)
137
+
138
+ expect(prompt.output.string).to eq([
139
+ "Overwrite Gemfile? (enter \"h\" for help) [y,n,a,\e[32md\e[0m,q,h] ",
140
+ "\e[1000D\e[K",
141
+ "Overwrite Gemfile? \e[32mShow diff\e[0m\n"
142
+ ].join)
143
+ end
144
+
145
+ it "specifies options through DSL and executes value" do
146
+ prompt.input << "\n"
147
+ prompt.input.rewind
148
+
149
+ result = prompt.expand('Overwrite Gemfile?') do |q|
150
+ q.choice key: 'y', name: 'Overwrite' do :ok end
151
+ q.choice key: 'n', name: 'Skip', value: :no
152
+ q.choice key: 'a', name: 'Overwrite all', value: :all
153
+ q.choice key: 'd', name: 'Show diff', value: :diff
154
+ q.choice key: 'q', name: 'Quit', value: :quit
155
+ end
156
+
157
+ expect(result).to eq(:ok)
158
+
159
+ expect(prompt.output.string).to eq([
160
+ "Overwrite Gemfile? (enter \"h\" for help) [\e[32my\e[0m,n,a,d,q,h] ",
161
+ "\e[1000D\e[K",
162
+ "Overwrite Gemfile? \e[32mOverwrite\e[0m\n"
163
+ ].join)
164
+ end
165
+
166
+ it "fails to expand due to lack of key attribute" do
167
+ choices = [{ name: 'Overwrite', value: :yes }]
168
+
169
+ expect {
170
+ prompt.expand('Overwrite Gemfile?', choices)
171
+ }.to raise_error(TTY::Prompt::ConfigurationError, /Choice Overwrite is missing a :key attribute/)
172
+ end
173
+
174
+ it "fails to expand due to wrong key length" do
175
+ choices = [{ key: 'long', name: 'Overwrite', value: :yes }]
176
+
177
+ expect {
178
+ prompt.expand('Overwrite Gemfile?', choices)
179
+ }.to raise_error(TTY::Prompt::ConfigurationError, /Choice key `long` is more than one character long/)
180
+ end
181
+
182
+ it "fails to expand due to reserve key" do
183
+ choices = [{ key: 'h', name: 'Overwrite', value: :yes }]
184
+
185
+ expect {
186
+ prompt.expand('Overwrite Gemfile?', choices)
187
+ }.to raise_error(TTY::Prompt::ConfigurationError, /Choice key `h` is reserved for help menu/)
188
+ end
189
+
190
+ it "fails to expand due to duplicate key" do
191
+ choices = [{ key: 'y', name: 'Overwrite', value: :yes },
192
+ { key: 'y', name: 'Change', value: :yes }]
193
+
194
+ expect {
195
+ prompt.expand('Overwrite Gemfile?', choices)
196
+ }.to raise_error(TTY::Prompt::ConfigurationError, /Choice key `y` is a duplicate/)
197
+ end
198
+ end