theme-juice 0.7.8 → 0.7.9

Sign up to get free protection for your applications and to get access to all the features.
Files changed (41) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +216 -204
  3. data/bin/tj +15 -15
  4. data/lib/theme-juice.rb +46 -46
  5. data/lib/theme-juice/cli.rb +248 -248
  6. data/lib/theme-juice/command.rb +20 -20
  7. data/lib/theme-juice/commands/create.rb +220 -221
  8. data/lib/theme-juice/commands/delete.rb +51 -51
  9. data/lib/theme-juice/commands/deploy.rb +20 -20
  10. data/lib/theme-juice/config.rb +68 -68
  11. data/lib/theme-juice/env.rb +25 -25
  12. data/lib/theme-juice/io.rb +295 -323
  13. data/lib/theme-juice/project.rb +35 -35
  14. data/lib/theme-juice/task.rb +43 -42
  15. data/lib/theme-juice/tasks/create_confirm.rb +33 -33
  16. data/lib/theme-juice/tasks/create_success.rb +42 -42
  17. data/lib/theme-juice/tasks/database.rb +50 -50
  18. data/lib/theme-juice/tasks/delete_confirm.rb +24 -24
  19. data/lib/theme-juice/tasks/delete_success.rb +31 -31
  20. data/lib/theme-juice/tasks/dns.rb +45 -45
  21. data/lib/theme-juice/tasks/dot_env.rb +53 -53
  22. data/lib/theme-juice/tasks/entry.rb +50 -50
  23. data/lib/theme-juice/tasks/hosts.rb +43 -43
  24. data/lib/theme-juice/tasks/import_database.rb +28 -28
  25. data/lib/theme-juice/tasks/landrush.rb +33 -33
  26. data/lib/theme-juice/tasks/list.rb +50 -50
  27. data/lib/theme-juice/tasks/location.rb +23 -23
  28. data/lib/theme-juice/tasks/nginx.rb +51 -51
  29. data/lib/theme-juice/tasks/repo.rb +49 -49
  30. data/lib/theme-juice/tasks/synced_folder.rb +30 -30
  31. data/lib/theme-juice/tasks/theme.rb +34 -34
  32. data/lib/theme-juice/tasks/vm.rb +30 -30
  33. data/lib/theme-juice/tasks/vm_customfile.rb +42 -42
  34. data/lib/theme-juice/tasks/vm_location.rb +32 -32
  35. data/lib/theme-juice/tasks/vm_plugins.rb +32 -32
  36. data/lib/theme-juice/tasks/vm_provision.rb +39 -39
  37. data/lib/theme-juice/tasks/vm_restart.rb +25 -25
  38. data/lib/theme-juice/tasks/wp_cli.rb +52 -52
  39. data/lib/theme-juice/util.rb +45 -45
  40. data/lib/theme-juice/version.rb +5 -5
  41. metadata +63 -6
@@ -1,51 +1,51 @@
1
- # encoding: UTF-8
2
-
3
- module ThemeJuice
4
- module Commands
5
- class Delete < Command
6
-
7
- def initialize(opts = {})
8
- super
9
-
10
- @project.name = @opts.fetch("name") { name }
11
- @project.url = @opts.fetch("url") { url }
12
- @project.db_drop = @opts.fetch("db_drop", false)
13
- @project.vm_restart = @opts.fetch("vm_restart", false)
14
- @project.vm_root = vm_root
15
- @project.vm_location = vm_location
16
- @project.vm_srv = vm_srv
17
-
18
- runner do |tasks|
19
- tasks << Tasks::DeleteConfirm.new
20
- tasks << Tasks::Database.new
21
- tasks << Tasks::VMLocation.new
22
- tasks << Tasks::SyncedFolder.new
23
- tasks << Tasks::DNS.new
24
- tasks << Tasks::DeleteSuccess.new
25
- end
26
- end
27
-
28
- private
29
-
30
- def name
31
- name = @io.prompt "What's the project name?"
32
-
33
- unless @list.projects.include? name
34
- @io.error "Project '#{name}' doesn't exist"
35
- end
36
-
37
- name
38
- end
39
-
40
- def url
41
- url = @io.prompt "What is the project's development url?", :default => "#{@project.name}.dev"
42
-
43
- unless @list.urls.include? url
44
- @io.notice "Project url '#{url}' doesn't exist within DNS records. Skipping..."
45
- end
46
-
47
- url
48
- end
49
- end
50
- end
51
- end
1
+ # encoding: UTF-8
2
+
3
+ module ThemeJuice
4
+ module Commands
5
+ class Delete < Command
6
+
7
+ def initialize(opts = {})
8
+ super
9
+
10
+ @project.name = @opts.fetch("name") { name }
11
+ @project.url = @opts.fetch("url") { url }
12
+ @project.db_drop = @opts.fetch("db_drop", false)
13
+ @project.vm_restart = @opts.fetch("vm_restart", false)
14
+ @project.vm_root = vm_root
15
+ @project.vm_location = vm_location
16
+ @project.vm_srv = vm_srv
17
+
18
+ runner do |tasks|
19
+ tasks << Tasks::DeleteConfirm.new
20
+ tasks << Tasks::Database.new
21
+ tasks << Tasks::VMLocation.new
22
+ tasks << Tasks::SyncedFolder.new
23
+ tasks << Tasks::DNS.new
24
+ tasks << Tasks::DeleteSuccess.new
25
+ end
26
+ end
27
+
28
+ private
29
+
30
+ def name
31
+ name = @io.prompt "What's the project name?"
32
+
33
+ unless @list.projects.include? name
34
+ @io.error "Project '#{name}' doesn't exist"
35
+ end
36
+
37
+ name
38
+ end
39
+
40
+ def url
41
+ url = @io.prompt "What is the project's development url?", :default => "#{@project.name}.dev"
42
+
43
+ unless @list.urls.include? url
44
+ @io.notice "Project url '#{url}' doesn't exist within DNS records. Skipping..."
45
+ end
46
+
47
+ url
48
+ end
49
+ end
50
+ end
51
+ end
@@ -1,20 +1,20 @@
1
- # encoding: UTF-8
2
-
3
- module ThemeJuice
4
- module Commands
5
- class Deploy < Command
6
-
7
- def initialize(opts = {})
8
- super
9
-
10
- @project.vm_root = vm_root
11
- @project.vm_location = vm_location
12
- @project.vm_srv = vm_srv
13
-
14
- runner do |tasks|
15
- @io.error "Not implemented"
16
- end
17
- end
18
- end
19
- end
20
- end
1
+ # encoding: UTF-8
2
+
3
+ module ThemeJuice
4
+ module Commands
5
+ class Deploy < Command
6
+
7
+ def initialize(opts = {})
8
+ super
9
+
10
+ @project.vm_root = vm_root
11
+ @project.vm_location = vm_location
12
+ @project.vm_srv = vm_srv
13
+
14
+ runner do |tasks|
15
+ @io.error "Not implemented"
16
+ end
17
+ end
18
+ end
19
+ end
20
+ end
@@ -1,68 +1,68 @@
1
- # encoding: UTF-8
2
-
3
- module ThemeJuice
4
- module Config
5
- @env = Env
6
- @io = IO
7
- @project = Project
8
- @util = Util.new
9
-
10
- def method_missing(method, *args, &block)
11
- @project.location ||= Dir.pwd
12
-
13
- begin
14
- config.fetch("commands", {})
15
- .fetch("#{method}") { @io.error("Command '#{method}' not found in config") }
16
- .each { |cmd| run format_command(cmd, *args) }
17
- rescue ::NoMethodError => err
18
- @io.error "Config file is invalid" do
19
- puts err
20
- end
21
- end
22
- end
23
-
24
- private
25
-
26
- def run(command)
27
- @util.inside @project.location do
28
- @util.run command, :verbose => @env.verbose
29
- end
30
- end
31
-
32
- def format_command(cmd, args = [])
33
- if multi_arg_regex =~ cmd
34
- cmd.gsub! multi_arg_regex, args.join(" ")
35
- else
36
- args.to_enum.with_index(1).each do |arg, i|
37
- cmd.gsub! single_arg_regex(i), arg
38
- end
39
- end
40
- cmd
41
- end
42
-
43
- def config
44
- begin
45
- YAML.load_file Dir["#{@project.location}/*"].select { |f| config_regex =~ File.basename(f) }.last ||
46
- @io.error("Config file not found in '#{@project.location}'")
47
- rescue ::Psych::SyntaxError => err
48
- @io.error "Config file contains invalid YAML" do
49
- puts err
50
- end
51
- end
52
- end
53
-
54
- def config_regex
55
- %r{^(((\.)?(tj)|((J|j)uicefile))(.y(a)?ml)?$)}
56
- end
57
-
58
- def multi_arg_regex
59
- %r{(%args%)|(%arguments%)}
60
- end
61
-
62
- def single_arg_regex(i)
63
- %r{(%arg#{i}%)|(%argument#{i}%)}
64
- end
65
-
66
- extend self
67
- end
68
- end
1
+ # encoding: UTF-8
2
+
3
+ module ThemeJuice
4
+ module Config
5
+ @env = Env
6
+ @io = IO
7
+ @project = Project
8
+ @util = Util.new
9
+
10
+ def method_missing(method, *args, &block)
11
+ @project.location ||= Dir.pwd
12
+
13
+ begin
14
+ config.fetch("commands", {})
15
+ .fetch("#{method}") { @io.error "Command '#{method}' not found in config", NotImplementedError }
16
+ .each { |cmd| run format_command(cmd, *args) }
17
+ rescue ::NoMethodError => err
18
+ @io.error "Config file is invalid", SyntaxError do
19
+ puts err
20
+ end
21
+ end
22
+ end
23
+
24
+ private
25
+
26
+ def run(command)
27
+ @util.inside @project.location do
28
+ @util.run command, :verbose => @env.verbose
29
+ end
30
+ end
31
+
32
+ def format_command(cmd, args = [])
33
+ if multi_arg_regex =~ cmd
34
+ cmd.gsub! multi_arg_regex, args.join(" ")
35
+ else
36
+ args.to_enum.with_index(1).each do |arg, i|
37
+ cmd.gsub! single_arg_regex(i), arg
38
+ end
39
+ end
40
+ cmd
41
+ end
42
+
43
+ def config
44
+ begin
45
+ YAML.load_file Dir["#{@project.location}/*"].select { |f| config_regex =~ File.basename(f) }.last ||
46
+ @io.error("Config file not found in '#{@project.location}'")
47
+ rescue ::Psych::SyntaxError => err
48
+ @io.error "Config file contains invalid YAML", SyntaxError do
49
+ puts err
50
+ end
51
+ end
52
+ end
53
+
54
+ def config_regex
55
+ %r{^(((\.)?(tj)|((J|j)uicefile))(.y(a)?ml)?$)}
56
+ end
57
+
58
+ def multi_arg_regex
59
+ %r{(%args%)|(%arguments%)}
60
+ end
61
+
62
+ def single_arg_regex(i)
63
+ %r{(%arg#{i}%)|(%argument#{i}%)}
64
+ end
65
+
66
+ extend self
67
+ end
68
+ end
@@ -1,25 +1,25 @@
1
- # encoding: UTF-8
2
-
3
- module ThemeJuice
4
- module Env
5
- attr_accessor :vm_path
6
- attr_accessor :vm_ip
7
- attr_accessor :yolo
8
- attr_accessor :boring
9
- attr_accessor :no_unicode
10
- attr_accessor :no_colors
11
- attr_accessor :no_animations
12
- attr_accessor :no_landrush
13
- attr_accessor :vm_prefix
14
- attr_accessor :verbose
15
- attr_accessor :dryrun
16
-
17
- def inspect
18
- res = []
19
- self.instance_variables.each { |k, _| res << "#{k[1..-1]}: #{instance_variable_get(k)}" }
20
- res.sort
21
- end
22
-
23
- extend self
24
- end
25
- end
1
+ # encoding: UTF-8
2
+
3
+ module ThemeJuice
4
+ module Env
5
+ attr_accessor :vm_path
6
+ attr_accessor :vm_ip
7
+ attr_accessor :vm_prefix
8
+ attr_accessor :no_unicode
9
+ attr_accessor :no_colors
10
+ attr_accessor :no_animations
11
+ attr_accessor :no_landrush
12
+ attr_accessor :boring
13
+ attr_accessor :yolo
14
+ attr_accessor :verbose
15
+ attr_accessor :dryrun
16
+
17
+ def inspect
18
+ res = []
19
+ self.instance_variables.each { |k, _| res << "#{k[1..-1]}: #{instance_variable_get(k)}" }
20
+ res.sort
21
+ end
22
+
23
+ extend self
24
+ end
25
+ end
@@ -1,323 +1,295 @@
1
- # encoding: UTF-8
2
-
3
- module ThemeJuice
4
- module IO
5
- include Thor::Shell
6
-
7
- ICONS = {
8
- :success => "✓", # "\u2713",
9
- :error => "↑", # "\u2191",
10
- :notice => "→", # "\u2192",
11
- :question => "•", # "\u2022",
12
- :general => "›", # "\u203A",
13
- :log => "…", # "\u2026",
14
- :restart => "↪", # "\u21AA",
15
- :selected => "•", # "\u2022",
16
- :unselected => "○", # "\u25CB",
17
- :fallback_success => "+",
18
- :fallback_error => "!",
19
- :fallback_notice => "!",
20
- :fallback_question => "?",
21
- :fallback_general => "-",
22
- :fallback_log => "...",
23
- :fallback_restart => "!",
24
- :fallback_selected => "[x]",
25
- :fallback_unselected => "[ ]",
26
- }
27
-
28
- KEYS = {
29
- "\e[A" => "up",
30
- "\e[B" => "down",
31
- "\e[C" => "right",
32
- "\e[D" => "left",
33
- "\003" => "ctrl+c",
34
- "\004" => "ctrl+d",
35
- "\e" => "escape",
36
- "\n" => "linefeed",
37
- "\r" => "return",
38
- " " => "space",
39
- }
40
-
41
- @state = nil
42
- @env = Env
43
-
44
- def speak(message, opts = {})
45
- format_message message, opts
46
- output_message
47
- end
48
-
49
- def prompt(question, *opts)
50
- format_message question, {
51
- :color => :blue,
52
- :icon => :question
53
- }
54
-
55
- opts.each do |opt|
56
-
57
- # if opt[:default]
58
- # opt[:default] = set_color(opt[:default], :black, :bold) unless @env.no_colors
59
- # end
60
-
61
- if opt[:indent]
62
- with(question) { |str| (" " * opt[:indent]) << str }
63
- end
64
-
65
- break
66
- end
67
-
68
- ask("#{question} :", *opts).gsub /\e\[\d+m/, ""
69
- end
70
-
71
- def agree?(question, opts = {})
72
- format_message question, {
73
- :color => opts.fetch("color", :blue),
74
- :icon => :question
75
- }
76
-
77
- if opts[:simple]
78
- yes? " :", opts.fetch("color", {})
79
- else
80
- yes? "#{question} (y/N) :"
81
- end
82
- end
83
-
84
- def log(message)
85
- speak message, {
86
- :color => :yellow,
87
- :icon => :log
88
- }
89
- end
90
-
91
- def success(message)
92
- speak message, {
93
- :color => [:black, :on_green, :bold],
94
- :icon => :success,
95
- :row => true
96
- }
97
- end
98
-
99
- def notice(message)
100
- speak message, {
101
- :color => [:black, :on_yellow],
102
- :icon => :notice,
103
- :row => true
104
- }
105
- end
106
-
107
- def error(message)
108
- speak message, {
109
- :color => [:white, :on_red],
110
- :icon => :error,
111
- :row => true
112
- }
113
-
114
- yield if block_given?
115
-
116
- exit 1
117
- end
118
-
119
- def hello(opts = {})
120
- speak "Welcome to Theme Juice!", {
121
- :color => [:black, :on_green, :bold],
122
- :row => true
123
- }.merge(opts)
124
- end
125
-
126
- def goodbye(opts = {})
127
-
128
- # Have some fun?
129
- goodbyes = [
130
- "Bye, bye, bye",
131
- "Adios, muchachos",
132
- "See ya later, alligator",
133
- "Peace...",
134
- "Later, homes",
135
- "I'll be back",
136
- "Victory is ours!",
137
- "May the force be with you",
138
- "Take a break, man...",
139
- "It's not me, it's you",
140
- "Go home, developer, you're drunk",
141
- "Okay, this is getting a little out of hand...",
142
- "I don't like it when you press my buttons",
143
- "Ouch!",
144
- ":(",
145
- ]
146
-
147
- speak goodbyes.sample, {
148
- :color => :yellow,
149
- :newline => true
150
- }.merge(opts)
151
-
152
- exit 130
153
- end
154
-
155
- def open_project(url)
156
- speak "Do you want to open up your new project at 'http://#{url}' now? (y/N)", {
157
- :color => [:black, :on_blue],
158
- :icon => :restart,
159
- :row => true
160
- }
161
-
162
- if agree? "", { :simple => true }
163
- OS.open_file_command "http://#{url}"
164
- end
165
- end
166
-
167
- def list(header, color, list)
168
- speak header, {
169
- :color => [:black, :"on_#{color}"],
170
- :icon => :notice,
171
- :row => true
172
- }
173
-
174
- list.each do |item|
175
- speak item, {
176
- :color => :"#{color}",
177
- :icon => :general
178
- }
179
- end
180
- end
181
-
182
- def choose(header, color, list)
183
- if OS.windows?
184
- ask header, {
185
- :limited_to => list,
186
- :color => color
187
- }
188
- else
189
- speak "#{header} (use arrow keys and press enter)", {
190
- :color => :"#{color}",
191
- :icon => :question
192
- }
193
-
194
- print "\n" * list.size
195
-
196
- selected = 0
197
- update_list_selection(list, color, selected)
198
-
199
- loop do
200
- key = read_key
201
- case key
202
- when "up"
203
- selected -= 1
204
- selected = list.size - 1 if selected < 0
205
- update_list_selection(list, color, selected)
206
- when "down"
207
- selected += 1
208
- selected = 0 if selected > list.size - 1
209
- update_list_selection(list, color, selected)
210
- when "return", "linefeed", "space"
211
- return list[selected]
212
- when "esc", "ctrl+c"
213
- goodbye(:newline => false)
214
- # else
215
- # speak key.inspect, { :color => :yellow }
216
- end
217
- end
218
- end
219
- end
220
-
221
- private
222
-
223
- def update_list_selection(list, color, selected = 0)
224
- print "\e[#{list.size}A"
225
-
226
- list.each_with_index do |item, i|
227
- icon = i == selected ? "selected" : "unselected"
228
- speak "#{item}", {
229
- :color => :"#{color}",
230
- :icon => :"#{icon}",
231
- :indent => 2
232
- }
233
- end
234
- end
235
-
236
- #
237
- # @see http://www.alecjacobson.com/weblog/?p=75
238
- #
239
- def read_key
240
- save_stty_state
241
- raw_stty_mode
242
-
243
- key = STDIN.getc.chr
244
-
245
- if key == "\e"
246
- thread = Thread.new { key += STDIN.getc.chr + STDIN.getc.chr }
247
- thread.join(0.001)
248
- thread.kill
249
- end
250
-
251
- KEYS[key] || key
252
- ensure
253
- restore_stty_state
254
- end
255
-
256
- def save_stty_state
257
- @state = %x(stty -g)
258
- end
259
-
260
- def raw_stty_mode
261
- %x(stty raw -echo)
262
- end
263
-
264
- def restore_stty_state
265
- %x(stty #{@state})
266
- end
267
-
268
- def format_message(message, opts = {})
269
- @message, @opts = message, opts
270
-
271
- format_message_icon
272
- format_message_newline
273
- format_message_row
274
- format_message_width
275
- format_message_color
276
- format_message_indent
277
-
278
- @message
279
- end
280
-
281
- def with(string)
282
- str = yield(string); string.clear; string << str
283
- end
284
-
285
- def format_message_icon
286
- icon = @env.no_unicode ? "fallback_#{@opts[:icon]}" : "#{@opts[:icon]}"
287
-
288
- if @opts[:icon]
289
- with(@message) { |msg| "#{ICONS[:"#{icon}"]}" << (@opts[:empty] ? nil : " #{msg}") }
290
- else
291
- with(@message) { |msg| "" << msg }
292
- end
293
- end
294
-
295
- def format_message_newline
296
- with(@message) { |msg| "\n" << msg } if @opts[:newline]
297
- end
298
-
299
- def format_message_color
300
- unless @env.no_colors
301
- with(@message) { |msg| set_color(msg, *@opts[:color]) } if @opts[:color]
302
- end
303
- end
304
-
305
- def format_message_row
306
- with(@message) { |msg| msg.ljust(terminal_width) } if @opts[:row]
307
- end
308
-
309
- def format_message_width
310
- with(@message) { |msg| msg.ljust(@opts[:width]) } if @opts[:width]
311
- end
312
-
313
- def format_message_indent
314
- with(@message) { |str| (" " * @opts[:indent]) << str } if @opts[:indent]
315
- end
316
-
317
- def output_message
318
- @opts[:quiet] ? @message : say(@message)
319
- end
320
-
321
- extend self
322
- end
323
- end
1
+ # encoding: UTF-8
2
+
3
+ module ThemeJuice
4
+ module IO
5
+ include Thor::Shell
6
+
7
+ ICONS = {
8
+ :success => "✓", # "\u2713",
9
+ :error => "↑", # "\u2191",
10
+ :notice => "→", # "\u2192",
11
+ :question => "•", # "\u2022",
12
+ :general => "›", # "\u203A",
13
+ :log => "…", # "\u2026",
14
+ :restart => "↪", # "\u21AA",
15
+ :selected => "•", # "\u2022",
16
+ :unselected => "○", # "\u25CB",
17
+ :fallback_success => "+",
18
+ :fallback_error => "!",
19
+ :fallback_notice => "!",
20
+ :fallback_question => "?",
21
+ :fallback_general => "-",
22
+ :fallback_log => "...",
23
+ :fallback_restart => "!",
24
+ :fallback_selected => "[x]",
25
+ :fallback_unselected => "[ ]",
26
+ }
27
+
28
+ KEYS = {
29
+ "\e[A" => "up",
30
+ "\e[B" => "down",
31
+ "w" => "w",
32
+ "s" => "s",
33
+ "\003" => "ctrl+c",
34
+ "\004" => "ctrl+d",
35
+ "\e" => "escape",
36
+ "\n" => "linefeed",
37
+ "\r" => "return",
38
+ " " => "space",
39
+ }
40
+
41
+ @env = Env
42
+ @sel = 0
43
+
44
+ def speak(message, opts = {})
45
+ format_message message, opts
46
+ output_message
47
+ end
48
+
49
+ def prompt(question, *opts)
50
+ format_message question, {
51
+ :color => :blue,
52
+ :icon => :question
53
+ }
54
+
55
+ opts.each do |opt|
56
+
57
+ # if opt[:default]
58
+ # opt[:default] = set_color(opt[:default], :black, :bold) unless @env.no_colors
59
+ # end
60
+
61
+ if opt[:indent]
62
+ with(question) { |str| (" " * opt[:indent]) << str }
63
+ end
64
+
65
+ break
66
+ end
67
+
68
+ ask("#{question} :", *opts).gsub /\e\[\d+m/, ""
69
+ end
70
+
71
+ def agree?(question, opts = {})
72
+ format_message question, {
73
+ :color => opts.fetch("color", :blue),
74
+ :icon => :question
75
+ }
76
+
77
+ if opts[:simple]
78
+ yes? " :", opts.fetch("color", {})
79
+ else
80
+ yes? "#{question} (y/N) :"
81
+ end
82
+ end
83
+
84
+ def log(message)
85
+ speak message, {
86
+ :color => :yellow,
87
+ :icon => :log
88
+ }
89
+ end
90
+
91
+ def success(message)
92
+ speak message, {
93
+ :color => [:black, :on_green, :bold],
94
+ :icon => :success,
95
+ :row => true
96
+ }
97
+ end
98
+
99
+ def notice(message)
100
+ speak message, {
101
+ :color => [:black, :on_yellow],
102
+ :icon => :notice,
103
+ :row => true
104
+ }
105
+ end
106
+
107
+ def error(message, code = SystemExit)
108
+ speak message, {
109
+ :color => [:white, :on_red],
110
+ :icon => :error,
111
+ :row => true
112
+ }
113
+
114
+ yield if block_given?
115
+
116
+ raise code
117
+ end
118
+
119
+ def hello(opts = {})
120
+ speak "Welcome to Theme Juice!", {
121
+ :color => [:black, :on_green, :bold],
122
+ :row => true
123
+ }.merge(opts)
124
+ end
125
+
126
+ def goodbye(opts = {})
127
+ goodbyes = [
128
+ "Bye, bye, bye",
129
+ "Adios, muchachos",
130
+ "See ya later, alligator",
131
+ "Peace...",
132
+ "Later, homes",
133
+ "I'll be back",
134
+ "Victory is ours!",
135
+ "May the force be with you",
136
+ "Take a break, man...",
137
+ "It's not me, it's you",
138
+ "Go home, developer, you're drunk",
139
+ "Okay, this is getting a little out of hand...",
140
+ "I don't like it when you press my buttons",
141
+ "Ouch!",
142
+ ":(",
143
+ ]
144
+
145
+ speak goodbyes.sample, {
146
+ :color => :yellow,
147
+ :newline => true
148
+ }.merge(opts)
149
+
150
+ exit 1
151
+ end
152
+
153
+ def list(header, color, list)
154
+ speak header, {
155
+ :color => [:black, :"on_#{color}"],
156
+ :icon => :notice,
157
+ :row => true
158
+ }
159
+
160
+ list.each do |item|
161
+ speak item, {
162
+ :color => :"#{color}",
163
+ :icon => :general
164
+ }
165
+ end
166
+ end
167
+
168
+ def choose(header, color, list)
169
+ speak "#{header} (#{choose_instructions})", {
170
+ :color => :"#{color}",
171
+ :icon => :question
172
+ }
173
+
174
+ list.each { puts }
175
+ update_selection list, color
176
+
177
+ loop do
178
+ key = read_key
179
+ case key
180
+ when "up", "w"
181
+ update_selection list, color, -1
182
+ when "down", "s"
183
+ update_selection list, color, 1
184
+ when "return", "linefeed", "space"
185
+ return list[@sel]
186
+ when "esc", "ctrl+c"
187
+ goodbye :newline => false
188
+ # else
189
+ # speak key.inspect, { :color => :yellow }
190
+ end
191
+ end
192
+ end
193
+
194
+ private
195
+
196
+ def choose_instructions
197
+ if OS.windows?
198
+ "use WASD keys and press space"
199
+ else
200
+ "use arrow keys and press enter"
201
+ end
202
+ end
203
+
204
+ def update_selection(list, color, diff = 0)
205
+ list.each { print "\e[1A" }
206
+
207
+ @sel += diff
208
+ @sel = 0 if @sel > list.size - 1
209
+ @sel = list.size - 1 if @sel < 0
210
+
211
+ list.each_with_index do |item, i|
212
+ icon = i == @sel ? "selected" : "unselected"
213
+ speak "#{item}", {
214
+ :color => :"#{color}",
215
+ :icon => :"#{icon}",
216
+ :indent => 2
217
+ }
218
+ end
219
+ end
220
+
221
+ def read_key
222
+ $stdin.noecho do |io|
223
+ io.raw!
224
+
225
+ key = io.getc.chr
226
+
227
+ if key == "\e"
228
+ key << io.getc.chr rescue nil
229
+ key << io.getc.chr rescue nil
230
+ end
231
+
232
+ io.cooked!
233
+
234
+ KEYS[key] || key
235
+ end
236
+ end
237
+
238
+ def format_message(message, opts = {})
239
+ @message, @opts = message, opts
240
+
241
+ format_message_icon
242
+ format_message_newline
243
+ format_message_row
244
+ format_message_width
245
+ format_message_color
246
+ format_message_indent
247
+
248
+ @message
249
+ end
250
+
251
+ def with(string)
252
+ str = yield(string); string.clear; string << str
253
+ end
254
+
255
+ def format_message_icon
256
+ icon = @env.no_unicode ? "fallback_#{@opts[:icon]}" : "#{@opts[:icon]}"
257
+
258
+ if @opts[:icon]
259
+ with(@message) { |msg| "#{ICONS[:"#{icon}"]}" << (@opts[:empty] ? nil : " #{msg}") }
260
+ else
261
+ with(@message) { |msg| "" << msg }
262
+ end
263
+ end
264
+
265
+ def format_message_newline
266
+ with(@message) { |msg| "\n" << msg } if @opts[:newline]
267
+ end
268
+
269
+ def format_message_color
270
+ unless @env.no_colors
271
+ with(@message) { |msg| set_color(msg, *@opts[:color]) } if @opts[:color]
272
+ end
273
+ end
274
+
275
+ def format_message_row
276
+ unless OS.windows?
277
+ with(@message) { |msg| msg.ljust(terminal_width) } if @opts[:row]
278
+ end
279
+ end
280
+
281
+ def format_message_width
282
+ with(@message) { |msg| msg.ljust(@opts[:width]) } if @opts[:width]
283
+ end
284
+
285
+ def format_message_indent
286
+ with(@message) { |str| (" " * @opts[:indent]) << str } if @opts[:indent]
287
+ end
288
+
289
+ def output_message
290
+ @opts[:quiet] ? @message : say(@message)
291
+ end
292
+
293
+ extend self
294
+ end
295
+ end