tty-prompt 0.4.0 → 0.5.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.
@@ -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