theme-juice 0.6.18 → 0.7.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.
- checksums.yaml +4 -4
- data/README.md +106 -75
- data/bin/tj +5 -5
- data/lib/theme-juice.rb +32 -16
- data/lib/theme-juice/cli.rb +191 -298
- data/lib/theme-juice/command.rb +14 -13
- data/lib/theme-juice/commands/create.rb +214 -9
- data/lib/theme-juice/commands/delete.rb +45 -10
- data/lib/theme-juice/commands/deploy.rb +20 -0
- data/lib/theme-juice/config.rb +43 -0
- data/lib/theme-juice/env.rb +25 -0
- data/lib/theme-juice/io.rb +323 -0
- data/lib/theme-juice/project.rb +35 -0
- data/lib/theme-juice/task.rb +42 -0
- data/lib/theme-juice/tasks/create_confirm.rb +33 -0
- data/lib/theme-juice/tasks/create_success.rb +42 -0
- data/lib/theme-juice/tasks/database.rb +50 -0
- data/lib/theme-juice/tasks/delete_confirm.rb +24 -0
- data/lib/theme-juice/tasks/delete_success.rb +31 -0
- data/lib/theme-juice/tasks/dns.rb +45 -0
- data/lib/theme-juice/tasks/dot_env.rb +53 -0
- data/lib/theme-juice/tasks/entry.rb +50 -0
- data/lib/theme-juice/tasks/hosts.rb +43 -0
- data/lib/theme-juice/tasks/import_database.rb +28 -0
- data/lib/theme-juice/tasks/landrush.rb +33 -0
- data/lib/theme-juice/tasks/list.rb +50 -0
- data/lib/theme-juice/tasks/location.rb +23 -0
- data/lib/theme-juice/tasks/nginx.rb +51 -0
- data/lib/theme-juice/tasks/repo.rb +49 -0
- data/lib/theme-juice/tasks/synced_folder.rb +30 -0
- data/lib/theme-juice/tasks/theme.rb +34 -0
- data/lib/theme-juice/tasks/vm.rb +30 -0
- data/lib/theme-juice/tasks/vm_customfile.rb +42 -0
- data/lib/theme-juice/tasks/vm_location.rb +32 -0
- data/lib/theme-juice/tasks/vm_plugins.rb +32 -0
- data/lib/theme-juice/tasks/vm_provision.rb +39 -0
- data/lib/theme-juice/tasks/vm_restart.rb +25 -0
- data/lib/theme-juice/tasks/wp_cli.rb +52 -0
- data/lib/theme-juice/util.rb +45 -0
- data/lib/theme-juice/version.rb +1 -1
- metadata +66 -34
- data/LICENSE +0 -339
- data/lib/theme-juice/commands/install.rb +0 -16
- data/lib/theme-juice/commands/list.rb +0 -16
- data/lib/theme-juice/commands/subcommand.rb +0 -16
- data/lib/theme-juice/environment.rb +0 -15
- data/lib/theme-juice/interaction.rb +0 -445
- data/lib/theme-juice/interactions/create.rb +0 -278
- data/lib/theme-juice/interactions/delete.rb +0 -48
- data/lib/theme-juice/interactions/teejay.rb +0 -12
- data/lib/theme-juice/service.rb +0 -224
- data/lib/theme-juice/services/config.rb +0 -44
- data/lib/theme-juice/services/create.rb +0 -376
- data/lib/theme-juice/services/delete.rb +0 -113
- data/lib/theme-juice/services/list.rb +0 -40
data/lib/theme-juice/command.rb
CHANGED
@@ -1,19 +1,20 @@
|
|
1
1
|
# encoding: UTF-8
|
2
2
|
|
3
3
|
module ThemeJuice
|
4
|
-
|
5
|
-
include ::Thor::Actions
|
6
|
-
include ::Thor::Shell
|
4
|
+
class Command < Task
|
7
5
|
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
6
|
+
def initialize(opts = {})
|
7
|
+
super
|
8
|
+
|
9
|
+
@list = Tasks::List.new
|
10
|
+
end
|
11
|
+
|
12
|
+
def execute
|
13
|
+
@tasks.each { |task| task.execute }
|
14
|
+
end
|
15
|
+
|
16
|
+
def unexecute
|
17
|
+
@tasks.each { |task| task.unexecute }
|
18
18
|
end
|
19
|
+
end
|
19
20
|
end
|
@@ -1,16 +1,221 @@
|
|
1
1
|
# encoding: UTF-8
|
2
2
|
|
3
3
|
module ThemeJuice
|
4
|
-
|
4
|
+
module Commands
|
5
|
+
class Create < Command
|
5
6
|
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
7
|
+
def initialize(opts = {})
|
8
|
+
super
|
9
|
+
|
10
|
+
@project.use_defaults = @opts.fetch("use_defaults", false)
|
11
|
+
@project.bare = @opts.fetch("bare", false)
|
12
|
+
@project.skip_repo = @opts.fetch("skip_repo", false)
|
13
|
+
@project.skip_db = @opts.fetch("skip_db", false)
|
14
|
+
@project.no_wp = @opts.fetch("no_wp", false)
|
15
|
+
@project.no_db = @opts.fetch("no_db", false)
|
16
|
+
@project.name = @opts.fetch("name") { name }
|
17
|
+
@project.location = @opts.fetch("location") { location }
|
18
|
+
@project.url = @opts.fetch("url") { url }
|
19
|
+
@project.theme = @opts.fetch("theme") { theme }
|
20
|
+
@project.repository = @opts.fetch("repository") { repository }
|
21
|
+
@project.db_host = @opts.fetch("db_host") { db_host }
|
22
|
+
@project.db_name = @opts.fetch("db_name") { db_name }
|
23
|
+
@project.db_user = @opts.fetch("db_user") { db_user }
|
24
|
+
@project.db_pass = @opts.fetch("db_pass") { db_pass }
|
25
|
+
@project.db_import = @opts.fetch("db_import") { db_import }
|
26
|
+
@project.vm_root = vm_root
|
27
|
+
@project.vm_location = vm_location
|
28
|
+
@project.vm_srv = vm_srv
|
29
|
+
|
30
|
+
runner do |tasks|
|
31
|
+
tasks << Tasks::CreateConfirm.new
|
32
|
+
tasks << Tasks::Location.new
|
33
|
+
tasks << Tasks::Theme.new
|
34
|
+
tasks << Tasks::VM.new
|
35
|
+
tasks << Tasks::VMPlugins.new
|
36
|
+
tasks << Tasks::VMLocation.new
|
37
|
+
tasks << Tasks::VMCustomfile.new
|
38
|
+
tasks << Tasks::Hosts.new
|
39
|
+
tasks << Tasks::Database.new
|
40
|
+
tasks << Tasks::Nginx.new
|
41
|
+
tasks << Tasks::DotEnv.new
|
42
|
+
tasks << Tasks::Landrush.new
|
43
|
+
tasks << Tasks::SyncedFolder.new
|
44
|
+
tasks << Tasks::DNS.new
|
45
|
+
tasks << Tasks::WPCLI.new
|
46
|
+
tasks << Tasks::Repo.new
|
47
|
+
tasks << Tasks::CreateSuccess.new
|
48
|
+
tasks << Tasks::ImportDatabase.new
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
private
|
53
|
+
|
54
|
+
def name
|
55
|
+
if @env.yolo
|
56
|
+
name = Faker::Internet.domain_word
|
57
|
+
else
|
58
|
+
name = @io.prompt "What's the project name? (letters, numbers and dashes only)"
|
59
|
+
end
|
60
|
+
|
61
|
+
valid_name? name
|
62
|
+
|
63
|
+
name
|
64
|
+
end
|
65
|
+
|
66
|
+
def valid_name?(name)
|
67
|
+
if name.empty?
|
68
|
+
@io.error "Project name '#{name}' looks like it's empty. Aborting mission."
|
69
|
+
end
|
70
|
+
|
71
|
+
"#{name}".match /[^0-9A-Za-z.\-]/ do |char|
|
72
|
+
@io.error "Project name contains an invalid character '#{char}'. This name is used internally for a ton of stuff, so that's not gonna work. Aborting mission."
|
73
|
+
end
|
74
|
+
|
75
|
+
true
|
76
|
+
end
|
77
|
+
|
78
|
+
def clean_name
|
79
|
+
"#{@project.name}".gsub(/[^\w]/, "_")[0..10]
|
80
|
+
end
|
81
|
+
|
82
|
+
def location
|
83
|
+
path = "#{Dir.pwd}/"
|
84
|
+
|
85
|
+
if @project.use_defaults
|
86
|
+
location = File.expand_path path
|
87
|
+
else
|
88
|
+
location = File.expand_path @io.prompt("Where do you want to setup the project?", :default => path, :path => true)
|
89
|
+
end
|
90
|
+
|
91
|
+
location
|
92
|
+
end
|
93
|
+
|
94
|
+
def url
|
95
|
+
if @project.use_defaults
|
96
|
+
url = "#{@project.name}.dev"
|
97
|
+
else
|
98
|
+
url = @io.prompt "What do you want the development url to be? (this should end in '.dev')", :default => "#{@project.name}.dev"
|
99
|
+
end
|
100
|
+
|
101
|
+
valid_url? url
|
102
|
+
|
103
|
+
url
|
104
|
+
end
|
105
|
+
|
106
|
+
def valid_url?(url)
|
107
|
+
unless "#{url}".match /(.dev)$/
|
108
|
+
@io.error "Your development url '#{url}' doesn't end with '.dev'. This is used internally by Landrush, so that's not gonna work. Aborting mission."
|
109
|
+
end
|
110
|
+
|
111
|
+
true
|
112
|
+
end
|
113
|
+
|
114
|
+
def theme
|
115
|
+
return false if @project.bare
|
116
|
+
|
117
|
+
theme = nil
|
118
|
+
themes = {
|
119
|
+
"theme-juice/theme-juice-starter" => "https://github.com/ezekg/theme-juice-starter.git",
|
120
|
+
"other" => nil,
|
121
|
+
"none" => nil
|
122
|
+
}
|
123
|
+
|
124
|
+
if @project.use_defaults
|
125
|
+
theme = themes["theme-juice/theme-juice-starter"]
|
126
|
+
else
|
127
|
+
choice = @io.choose "Which starter theme would you like to use?", :blue, themes.keys
|
128
|
+
|
129
|
+
case choice
|
130
|
+
when "theme-juice/theme-juice-starter"
|
131
|
+
@io.success "Awesome choice!"
|
132
|
+
when "other"
|
133
|
+
themes[choice] = @io.prompt "What is the repository URL for the starter theme that you would like to clone?"
|
134
|
+
when "none"
|
135
|
+
@io.notice "Next time you need to create a project without a starter theme, you can just run the 'setup' command instead."
|
136
|
+
@project.bare = true
|
137
|
+
end
|
138
|
+
|
139
|
+
theme = themes[choice]
|
140
|
+
end
|
141
|
+
|
142
|
+
theme
|
143
|
+
end
|
144
|
+
|
145
|
+
def repository
|
146
|
+
return false if @project.skip_repo || @project.use_defaults
|
147
|
+
|
148
|
+
if @io.agree? "Would you like to initialize a new Git repository?"
|
149
|
+
repo = @io.prompt "What is the repository's remote URL?", :indent => 2
|
150
|
+
else
|
151
|
+
repo = false
|
152
|
+
end
|
153
|
+
|
154
|
+
repo
|
155
|
+
end
|
156
|
+
|
157
|
+
def db_host
|
158
|
+
return false if @project.no_db || @project.no_wp
|
159
|
+
|
160
|
+
if @project.skip_db || @project.use_defaults
|
161
|
+
db_host = "vvv"
|
162
|
+
else
|
163
|
+
db_host = @io.prompt "Database host", :default => "vvv"
|
164
|
+
end
|
165
|
+
|
166
|
+
db_host
|
167
|
+
end
|
168
|
+
|
169
|
+
def db_name
|
170
|
+
return false if @project.no_db || @project.no_wp
|
171
|
+
|
172
|
+
if @project.skip_db || @project.use_defaults
|
173
|
+
db_name = "#{clean_name}_db"
|
174
|
+
else
|
175
|
+
db_name = @io.prompt "Database name", :default => "#{clean_name}_db"
|
14
176
|
end
|
177
|
+
|
178
|
+
db_name
|
179
|
+
end
|
180
|
+
|
181
|
+
def db_user
|
182
|
+
return false if @project.no_db || @project.no_wp
|
183
|
+
|
184
|
+
if @project.skip_db || @project.use_defaults
|
185
|
+
db_user = "#{clean_name}_user"
|
186
|
+
else
|
187
|
+
db_user = @io.prompt "Database username", :default => "#{clean_name}_user"
|
188
|
+
end
|
189
|
+
|
190
|
+
db_user
|
191
|
+
end
|
192
|
+
|
193
|
+
def db_pass
|
194
|
+
return false if @project.no_db || @project.no_wp
|
195
|
+
|
196
|
+
pass = Faker::Internet.password 24
|
197
|
+
|
198
|
+
if @project.skip_db || @project.use_defaults
|
199
|
+
db_pass = pass
|
200
|
+
else
|
201
|
+
db_pass = @io.prompt "Database password", :default => pass
|
202
|
+
end
|
203
|
+
|
204
|
+
db_pass
|
205
|
+
end
|
206
|
+
|
207
|
+
def db_import
|
208
|
+
return false if @project.no_db || @project.no_wp || @project.use_defaults
|
209
|
+
|
210
|
+
if @io.agree? "Would you like to import an existing database?"
|
211
|
+
db = @io.prompt "Where is the database file?", {
|
212
|
+
:indent => 2, :path => true }
|
213
|
+
else
|
214
|
+
db = false
|
215
|
+
end
|
216
|
+
|
217
|
+
db
|
218
|
+
end
|
15
219
|
end
|
220
|
+
end
|
16
221
|
end
|
@@ -1,16 +1,51 @@
|
|
1
1
|
# encoding: UTF-8
|
2
2
|
|
3
3
|
module ThemeJuice
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
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..."
|
14
45
|
end
|
46
|
+
|
47
|
+
url
|
48
|
+
end
|
15
49
|
end
|
50
|
+
end
|
16
51
|
end
|
@@ -0,0 +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
|
@@ -0,0 +1,43 @@
|
|
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
|
+
config.fetch("commands", {})
|
14
|
+
.fetch(method.to_s) { @io.error("Command '#{method}' not found in config") }
|
15
|
+
.each { |cmd| run "#{cmd} #{args.join(" ") unless args.empty?}" }
|
16
|
+
end
|
17
|
+
|
18
|
+
private
|
19
|
+
|
20
|
+
def run(command)
|
21
|
+
@util.inside @project.location do
|
22
|
+
@util.run command, :verbose => @env.verbose
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
def config
|
27
|
+
begin
|
28
|
+
YAML.load_file Dir["#{@project.location}/*"].select { |f| regex =~ File.basename(f) }.last ||
|
29
|
+
@io.error("Config file not found in '#{@project.location}'")
|
30
|
+
rescue ::Psych::SyntaxError => err
|
31
|
+
@io.error "Config file is invalid" do
|
32
|
+
puts err
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
def regex
|
38
|
+
%r{^((\.)?(tj.y(a)?ml)|((J|j)uicefile))}
|
39
|
+
end
|
40
|
+
|
41
|
+
extend self
|
42
|
+
end
|
43
|
+
end
|
@@ -0,0 +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
|
@@ -0,0 +1,323 @@
|
|
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
|