engineyard 0.5.3 → 0.5.4
Sign up to get free protection for your applications and to get access to all the features.
- data/lib/engineyard/cli.rb +71 -44
- data/lib/engineyard/cli/recipes.rb +17 -17
- data/lib/engineyard/cli/ui.rb +4 -8
- data/lib/engineyard/cli/web.rb +14 -15
- data/lib/engineyard/model/environment.rb +1 -1
- data/lib/engineyard/model/instance.rb +16 -4
- data/lib/engineyard/thor.rb +24 -18
- data/lib/engineyard/vendor/thor.rb +270 -0
- data/lib/engineyard/vendor/thor/actions.rb +297 -0
- data/lib/engineyard/vendor/thor/actions/create_file.rb +105 -0
- data/lib/engineyard/vendor/thor/actions/directory.rb +93 -0
- data/lib/engineyard/vendor/thor/actions/empty_directory.rb +134 -0
- data/lib/engineyard/vendor/thor/actions/file_manipulation.rb +229 -0
- data/lib/engineyard/vendor/thor/actions/inject_into_file.rb +104 -0
- data/lib/engineyard/vendor/thor/base.rb +540 -0
- data/lib/engineyard/vendor/thor/core_ext/file_binary_read.rb +9 -0
- data/lib/engineyard/vendor/thor/core_ext/hash_with_indifferent_access.rb +75 -0
- data/lib/engineyard/vendor/thor/core_ext/ordered_hash.rb +100 -0
- data/lib/engineyard/vendor/thor/error.rb +30 -0
- data/lib/engineyard/vendor/thor/group.rb +271 -0
- data/lib/engineyard/vendor/thor/invocation.rb +180 -0
- data/lib/engineyard/vendor/thor/parser.rb +4 -0
- data/lib/engineyard/vendor/thor/parser/argument.rb +67 -0
- data/lib/engineyard/vendor/thor/parser/arguments.rb +161 -0
- data/lib/engineyard/vendor/thor/parser/option.rb +128 -0
- data/lib/engineyard/vendor/thor/parser/options.rb +164 -0
- data/lib/engineyard/vendor/thor/rake_compat.rb +66 -0
- data/lib/engineyard/vendor/thor/runner.rb +314 -0
- data/lib/engineyard/vendor/thor/shell.rb +83 -0
- data/lib/engineyard/vendor/thor/shell/basic.rb +268 -0
- data/lib/engineyard/vendor/thor/shell/color.rb +108 -0
- data/lib/engineyard/vendor/thor/task.rb +102 -0
- data/lib/engineyard/vendor/thor/util.rb +229 -0
- data/lib/engineyard/vendor/thor/version.rb +3 -0
- data/lib/engineyard/version.rb +1 -1
- data/spec/ey/deploy_spec.rb +24 -5
- data/spec/ey/ey_spec.rb +1 -1
- data/spec/ey/rollback_spec.rb +7 -0
- data/spec/spec_helper.rb +13 -0
- data/spec/support/git_repo.rb +10 -7
- data/spec/support/helpers.rb +2 -2
- metadata +30 -4
- data/lib/engineyard/cli/thor_fixes.rb +0 -26
data/lib/engineyard/cli.rb
CHANGED
@@ -17,17 +17,17 @@ module EY
|
|
17
17
|
super
|
18
18
|
end
|
19
19
|
|
20
|
-
desc "deploy [--environment ENVIRONMENT] [--ref GIT-REF]",
|
21
|
-
Deploy specified branch
|
22
|
-
|
23
|
-
This command must be run with the current directory containing the app to be
|
24
|
-
deployed. If ey.yml specifies a default branch then the ref parameter can be
|
25
|
-
omitted. Furthermore, if a default branch is specified but a different command
|
26
|
-
is supplied the deploy will fail unless --force is used.
|
27
|
-
|
28
|
-
Migrations are run by default with 'rake db:migrate'. A different command can be
|
29
|
-
specified via --migrate "ruby do_migrations.rb". Migrations can also be skipped
|
30
|
-
entirely by using --no-migrate.
|
20
|
+
desc "deploy [--environment ENVIRONMENT] [--ref GIT-REF]",
|
21
|
+
"Deploy specified branch, tag, or sha to specified environment."
|
22
|
+
long_desc <<-DESC
|
23
|
+
This command must be run with the current directory containing the app to be
|
24
|
+
deployed. If ey.yml specifies a default branch then the ref parameter can be
|
25
|
+
omitted. Furthermore, if a default branch is specified but a different command
|
26
|
+
is supplied the deploy will fail unless --force is used.
|
27
|
+
|
28
|
+
Migrations are run by default with 'rake db:migrate'. A different command can be
|
29
|
+
specified via --migrate "ruby do_migrations.rb". Migrations can also be skipped
|
30
|
+
entirely by using --no-migrate.
|
31
31
|
DESC
|
32
32
|
method_option :force, :type => :boolean, :aliases => %w(-f),
|
33
33
|
:desc => "Force a deploy of the specified branch even if a default is set"
|
@@ -70,11 +70,10 @@ entirely by using --no-migrate.
|
|
70
70
|
raise exists ? EnvironmentUnlinkedError.new(options[:environment]) : e
|
71
71
|
end
|
72
72
|
|
73
|
-
desc "environments [--all]",
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
used, all environments are displayed instead.
|
73
|
+
desc "environments [--all]", "List environments."
|
74
|
+
long_desc <<-DESC
|
75
|
+
By default, environments for this app are displayed. The --all option will
|
76
|
+
display all environments, including those for this app.
|
78
77
|
DESC
|
79
78
|
|
80
79
|
method_option :all, :type => :boolean, :aliases => %(-a)
|
@@ -85,16 +84,15 @@ used, all environments are displayed instead.
|
|
85
84
|
end
|
86
85
|
map "envs" => :environments
|
87
86
|
|
88
|
-
desc "rebuild [--environment ENVIRONMENT]",
|
89
|
-
|
87
|
+
desc "rebuild [--environment ENVIRONMENT]", "Rebuild specified environment."
|
88
|
+
long_desc <<-DESC
|
89
|
+
Engine Yard's main configuration run occurs on all servers. Mainly used to fix
|
90
|
+
failed configuration of new or existing servers, or to update servers to latest
|
91
|
+
Engine Yard stack (e.g. to apply an Engine Yard supplied security
|
92
|
+
patch).
|
90
93
|
|
91
|
-
|
92
|
-
|
93
|
-
Engine Yard stack (e.g. to apply an Engine Yard supplied security
|
94
|
-
patch).
|
95
|
-
|
96
|
-
Note that uploaded recipes are also run after the main configuration run has
|
97
|
-
successfully completed.
|
94
|
+
Note that uploaded recipes are also run after the main configuration run has
|
95
|
+
successfully completed.
|
98
96
|
DESC
|
99
97
|
|
100
98
|
method_option :environment, :type => :string, :aliases => %w(-e),
|
@@ -105,12 +103,11 @@ successfully completed.
|
|
105
103
|
env.rebuild
|
106
104
|
end
|
107
105
|
|
108
|
-
desc "rollback [--environment ENVIRONMENT]",
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
DESC
|
106
|
+
desc "rollback [--environment ENVIRONMENT]", "Rollback to the previous deploy."
|
107
|
+
long_desc <<-DESC
|
108
|
+
Uses code from previous deploy in the "/data/APP_NAME/releases" directory on
|
109
|
+
remote server(s) to restart application servers.
|
110
|
+
DESC
|
114
111
|
method_option :environment, :type => :string, :aliases => %w(-e),
|
115
112
|
:desc => "Environment in which to roll back the current application"
|
116
113
|
def rollback
|
@@ -127,11 +124,10 @@ remote server(s) to restart application servers.
|
|
127
124
|
end
|
128
125
|
end
|
129
126
|
|
130
|
-
desc "ssh [--environment ENVIRONMENT]",
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
environments with clusters, a session will be opened to the application master.
|
127
|
+
desc "ssh [--environment ENVIRONMENT]", "Open an ssh session."
|
128
|
+
long_desc <<-DESC
|
129
|
+
If the environment contains just one server, a session to it will be opened. For
|
130
|
+
environments with clusters, a session will be opened to the application master.
|
135
131
|
DESC
|
136
132
|
method_option :environment, :type => :string, :aliases => %w(-e),
|
137
133
|
:desc => "Environment to ssh into"
|
@@ -145,12 +141,11 @@ environments with clusters, a session will be opened to the application master.
|
|
145
141
|
end
|
146
142
|
end
|
147
143
|
|
148
|
-
desc "logs [--environment ENVIRONMENT]",
|
149
|
-
|
150
|
-
|
151
|
-
|
152
|
-
|
153
|
-
displayed beneath the main configuration logs.
|
144
|
+
desc "logs [--environment ENVIRONMENT]", "Retrieve the latest logs for an environment."
|
145
|
+
long_desc <<-DESC
|
146
|
+
Displays Engine Yard configuration logs for all servers in the environment. If
|
147
|
+
recipes were uploaded to the environment & run, their logs will also be
|
148
|
+
displayed beneath the main configuration logs.
|
154
149
|
DESC
|
155
150
|
method_option :environment, :type => :string, :aliases => %w(-e),
|
156
151
|
:desc => "Environment with the interesting logs"
|
@@ -186,13 +181,45 @@ displayed beneath the main configuration logs.
|
|
186
181
|
desc "help [COMMAND]", "Describe all commands or one specific command."
|
187
182
|
def help(*cmds)
|
188
183
|
if cmds.empty?
|
189
|
-
|
190
|
-
|
184
|
+
base = self.class.send(:banner_base)
|
185
|
+
list = self.class.printable_tasks
|
186
|
+
|
187
|
+
EY.ui.say "Usage:"
|
188
|
+
EY.ui.say " #{base} [--help] [--version] COMMAND [ARGS]"
|
189
|
+
EY.ui.say
|
190
|
+
|
191
|
+
EY.ui.say "Deploy commands:"
|
192
|
+
deploy_cmds = %w(deploy environments logs rebuild rollback)
|
193
|
+
deploy_cmds.map! do |name|
|
194
|
+
list.find{|task| task[0] =~ /^#{base} #{name}/ }
|
195
|
+
end
|
196
|
+
list -= deploy_cmds
|
197
|
+
EY.ui.print_help(deploy_cmds)
|
198
|
+
EY.ui.say
|
199
|
+
|
200
|
+
EY::Thor.subcommands.each do |name, klass|
|
201
|
+
list.reject!{|cmd| cmd[0] =~ /^#{base} #{name}/}
|
202
|
+
EY.ui.say "#{name.capitalize} commands:"
|
203
|
+
tasks = klass.printable_tasks.reject{|t| t[0] =~ /help$/ }
|
204
|
+
EY.ui.print_help(tasks)
|
205
|
+
EY.ui.say
|
206
|
+
end
|
207
|
+
|
208
|
+
%w(help version).each{|n| list.reject!{|c| c[0] =~ /^#{base} #{n}/ } }
|
209
|
+
if list.any?
|
210
|
+
EY.ui.say "Other commands:"
|
211
|
+
EY.ui.print_help(list)
|
212
|
+
EY.ui.say
|
213
|
+
end
|
214
|
+
|
215
|
+
self.class.send(:class_options_help, shell)
|
216
|
+
EY.ui.say "See '#{base} help COMMAND' for more information on a specific command."
|
191
217
|
elsif klass = EY::Thor.subcommands[cmds.first]
|
192
218
|
klass.new.help(*cmds[1..-1])
|
193
219
|
else
|
194
220
|
super
|
195
221
|
end
|
196
222
|
end
|
223
|
+
|
197
224
|
end # CLI
|
198
225
|
end # EY
|
@@ -1,11 +1,11 @@
|
|
1
1
|
module EY
|
2
2
|
class CLI
|
3
3
|
class Recipes < EY::Thor
|
4
|
-
desc "
|
5
|
-
Run uploaded chef recipes on specified environment.
|
6
|
-
|
7
|
-
This is similar to '#{banner_base} rebuild' except Engine Yard's main
|
8
|
-
configuration step is skipped.
|
4
|
+
desc "apply [--environment ENVIRONMENT]",
|
5
|
+
"Run uploaded chef recipes on specified environment."
|
6
|
+
long_desc <<-DESC
|
7
|
+
This is similar to '#{banner_base} rebuild' except Engine Yard's main
|
8
|
+
configuration step is skipped.
|
9
9
|
DESC
|
10
10
|
|
11
11
|
method_option :environment, :type => :string, :aliases => %w(-e),
|
@@ -16,11 +16,11 @@ configuration step is skipped.
|
|
16
16
|
EY.ui.say "Uploaded recipes started for #{environment.name}"
|
17
17
|
end
|
18
18
|
|
19
|
-
desc "
|
20
|
-
Upload custom chef recipes to specified environment.
|
21
|
-
|
22
|
-
The current directory should contain a subdirectory named "cookbooks" to be
|
23
|
-
uploaded.
|
19
|
+
desc "upload [--environment ENVIRONMENT]",
|
20
|
+
"Upload custom chef recipes to specified environment."
|
21
|
+
long_desc <<-DESC
|
22
|
+
The current directory should contain a subdirectory named "cookbooks" to be
|
23
|
+
uploaded.
|
24
24
|
DESC
|
25
25
|
|
26
26
|
method_option :environment, :type => :string, :aliases => %w(-e),
|
@@ -31,14 +31,14 @@ uploaded.
|
|
31
31
|
EY.ui.say "Recipes uploaded successfully for #{environment.name}"
|
32
32
|
end
|
33
33
|
|
34
|
-
desc "
|
35
|
-
Download custom chef recipes from ENVIRONMENT into the current directory.
|
34
|
+
desc "download [--environment ENVIRONMENT]",
|
35
|
+
"Download custom chef recipes from ENVIRONMENT into the current directory."
|
36
|
+
long_desc <<-DESC
|
37
|
+
The recipes will be unpacked into a directory called "cookbooks" in the
|
38
|
+
current directory.
|
36
39
|
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
If the cookbooks directory already exists, an error will be raised.
|
41
|
-
DESC
|
40
|
+
If the cookbooks directory already exists, an error will be raised.
|
41
|
+
DESC
|
42
42
|
method_option :environment, :type => :string, :aliases => %w(-e),
|
43
43
|
:desc => "Environment for which to download the recipes"
|
44
44
|
def download
|
data/lib/engineyard/cli/ui.rb
CHANGED
@@ -43,14 +43,6 @@ module EY
|
|
43
43
|
end
|
44
44
|
end
|
45
45
|
|
46
|
-
def say(*args)
|
47
|
-
if args.first == "Tasks:"
|
48
|
-
super "Commands:"
|
49
|
-
else
|
50
|
-
super
|
51
|
-
end
|
52
|
-
end
|
53
|
-
|
54
46
|
def ask(message, password = false)
|
55
47
|
begin
|
56
48
|
require 'highline'
|
@@ -104,6 +96,10 @@ module EY
|
|
104
96
|
end
|
105
97
|
end
|
106
98
|
|
99
|
+
def print_help(table)
|
100
|
+
print_table(table, :ident => 2, :truncate => true, :colwidth => 20)
|
101
|
+
end
|
102
|
+
|
107
103
|
def set_color(string, color, bold=false)
|
108
104
|
$stdout.tty? ? super : string
|
109
105
|
end
|
data/lib/engineyard/cli/web.rb
CHANGED
@@ -1,9 +1,8 @@
|
|
1
1
|
module EY
|
2
2
|
class CLI
|
3
3
|
class Web < EY::Thor
|
4
|
-
desc "
|
5
|
-
|
6
|
-
HELP
|
4
|
+
desc "enable [--environment/-e ENVIRONMENT]",
|
5
|
+
"Remove the maintenance page for this application in the given environment."
|
7
6
|
method_option :environment, :type => :string, :aliases => %w(-e),
|
8
7
|
:desc => "Environment on which to put up the maintenance page"
|
9
8
|
def enable
|
@@ -14,19 +13,19 @@ Take down the maintenance page for the current application in the specified envi
|
|
14
13
|
environment.take_down_maintenance_page(app)
|
15
14
|
end
|
16
15
|
|
17
|
-
desc "
|
18
|
-
Put up the maintenance page for
|
16
|
+
desc "disable [--environment/-e ENVIRONMENT]",
|
17
|
+
"Put up the maintenance page for this application in the given environment."
|
18
|
+
long_desc <<-DESC
|
19
|
+
The maintenance page is taken from the app currently being deployed. This means
|
20
|
+
that you can customize maintenance pages to tell users the reason for downtime
|
21
|
+
on every particular deploy.
|
19
22
|
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
* public/maintenance.html.tmp
|
27
|
-
* public/maintenance.html
|
28
|
-
* public/system/maintenance.html.default
|
29
|
-
HELP
|
23
|
+
Maintenance pages searched for in order of decreasing priority:
|
24
|
+
* public/maintenance.html.custom
|
25
|
+
* public/maintenance.html.tmp
|
26
|
+
* public/maintenance.html
|
27
|
+
* public/system/maintenance.html.default
|
28
|
+
DESC
|
30
29
|
method_option :environment, :type => :string, :aliases => %w(-e),
|
31
30
|
:desc => "Environment on which to take down the maintenance page"
|
32
31
|
def disable
|
@@ -1,6 +1,6 @@
|
|
1
1
|
module EY
|
2
2
|
module Model
|
3
|
-
class Environment < ApiStruct.new(:id, :name, :instances, :instances_count, :apps, :app_master, :username, :api)
|
3
|
+
class Environment < ApiStruct.new(:id, :name, :instances, :instances_count, :apps, :app_master, :username, :stack_name, :api)
|
4
4
|
def self.from_hash(hash)
|
5
5
|
super.tap do |env|
|
6
6
|
env.username = hash['ssh_username']
|
@@ -32,8 +32,9 @@ exit(17) # required_version < current_version
|
|
32
32
|
|
33
33
|
def deploy(app, ref, migration_command=nil, extra_configuration=nil)
|
34
34
|
deploy_args = [
|
35
|
-
'--app',
|
36
|
-
'--repo',
|
35
|
+
'--app', app.name,
|
36
|
+
'--repo', app.repository_uri,
|
37
|
+
'--stack', environment.stack_name,
|
37
38
|
'--branch', ref,
|
38
39
|
]
|
39
40
|
|
@@ -49,7 +50,10 @@ exit(17) # required_version < current_version
|
|
49
50
|
end
|
50
51
|
|
51
52
|
def rollback(app, extra_configuration=nil)
|
52
|
-
deploy_args = ['rollback',
|
53
|
+
deploy_args = ['rollback',
|
54
|
+
'--app', app.name,
|
55
|
+
'--stack', environment.stack_name,
|
56
|
+
]
|
53
57
|
|
54
58
|
if extra_configuration
|
55
59
|
deploy_args << '--config' << extra_configuration.to_json
|
@@ -99,7 +103,15 @@ exit(17) # required_version < current_version
|
|
99
103
|
end
|
100
104
|
|
101
105
|
def install_ey_deploy
|
102
|
-
ssh(Escape.shell_command([
|
106
|
+
ssh(Escape.shell_command([
|
107
|
+
'sudo', 'sh', '-c',
|
108
|
+
# rubygems looks at *.gem in its current directory for
|
109
|
+
# installation candidates, so we have to make sure it
|
110
|
+
# runs from a directory with no gem files in it.
|
111
|
+
#
|
112
|
+
# rubygems help suggests that --remote will disable this
|
113
|
+
# behavior, but it doesn't.
|
114
|
+
"cd `mktemp -d` && #{gem_path} install ey-deploy --no-rdoc --no-ri -v '#{EYSD_VERSION}'"]))
|
103
115
|
end
|
104
116
|
|
105
117
|
def upgrade_ey_deploy
|
data/lib/engineyard/thor.rb
CHANGED
@@ -1,5 +1,5 @@
|
|
1
|
+
$LOAD_PATH.unshift File.expand_path("../vendor", __FILE__)
|
1
2
|
require 'thor'
|
2
|
-
require 'engineyard/cli/thor_fixes'
|
3
3
|
|
4
4
|
module EY
|
5
5
|
class Thor < ::Thor
|
@@ -21,17 +21,22 @@ module EY
|
|
21
21
|
end
|
22
22
|
|
23
23
|
def self.subcommand_help(cmd)
|
24
|
-
desc "
|
25
|
-
|
24
|
+
desc "#{cmd} help [COMMAND]", "Describe all subcommands or one specific subcommand."
|
26
25
|
class_eval <<-RUBY
|
27
|
-
def help(*args)
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
26
|
+
def help(*args)
|
27
|
+
if args.empty?
|
28
|
+
EY.ui.say "usage: #{banner_base} #{cmd} COMMAND"
|
29
|
+
EY.ui.say
|
30
|
+
subcommands = self.class.printable_tasks.sort_by{|s| s[0] }
|
31
|
+
subcommands.reject!{|t| t[0] =~ /#{cmd} help$/}
|
32
|
+
EY.ui.print_help(subcommands)
|
33
|
+
EY.ui.say self.class.send(:class_options_help, EY.ui)
|
34
|
+
EY.ui.say "See #{banner_base} #{cmd} help COMMAND" +
|
35
|
+
" for more information on a specific subcommand." if args.empty?
|
36
|
+
else
|
37
|
+
super
|
38
|
+
end
|
39
|
+
end
|
35
40
|
RUBY
|
36
41
|
end
|
37
42
|
|
@@ -39,13 +44,14 @@ end
|
|
39
44
|
@@original_args[1..-1]
|
40
45
|
end
|
41
46
|
|
42
|
-
def self.
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
47
|
+
def self.banner_base
|
48
|
+
"ey"
|
49
|
+
end
|
50
|
+
|
51
|
+
def self.banner(task, task_help = false)
|
52
|
+
scmd = EY::Thor.subcommands.invert[self]
|
53
|
+
task = (task_help ? task.formatted_usage(self, false) : task.name)
|
54
|
+
[banner_base, scmd, task].compact.join(" ")
|
49
55
|
end
|
50
56
|
end
|
51
57
|
|
@@ -0,0 +1,270 @@
|
|
1
|
+
require 'thor/base'
|
2
|
+
|
3
|
+
# TODO: Update thor to allow for git-style CLI (git bisect run)
|
4
|
+
class Thor
|
5
|
+
class << self
|
6
|
+
# Sets the default task when thor is executed without an explicit task to be called.
|
7
|
+
#
|
8
|
+
# ==== Parameters
|
9
|
+
# meth<Symbol>:: name of the defaut task
|
10
|
+
#
|
11
|
+
def default_task(meth=nil)
|
12
|
+
case meth
|
13
|
+
when :none
|
14
|
+
@default_task = 'help'
|
15
|
+
when nil
|
16
|
+
@default_task ||= from_superclass(:default_task, 'help')
|
17
|
+
else
|
18
|
+
@default_task = meth.to_s
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
# Defines the usage and the description of the next task.
|
23
|
+
#
|
24
|
+
# ==== Parameters
|
25
|
+
# usage<String>
|
26
|
+
# description<String>
|
27
|
+
#
|
28
|
+
def desc(usage, description, options={})
|
29
|
+
if options[:for]
|
30
|
+
task = find_and_refresh_task(options[:for])
|
31
|
+
task.usage = usage if usage
|
32
|
+
task.description = description if description
|
33
|
+
else
|
34
|
+
@usage, @desc = usage, description
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
# Defines the long description of the next task.
|
39
|
+
#
|
40
|
+
# ==== Parameters
|
41
|
+
# long description<String>
|
42
|
+
#
|
43
|
+
def long_desc(long_description, options={})
|
44
|
+
if options[:for]
|
45
|
+
task = find_and_refresh_task(options[:for])
|
46
|
+
task.long_description = long_description if long_description
|
47
|
+
else
|
48
|
+
@long_desc = long_description
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
# Maps an input to a task. If you define:
|
53
|
+
#
|
54
|
+
# map "-T" => "list"
|
55
|
+
#
|
56
|
+
# Running:
|
57
|
+
#
|
58
|
+
# thor -T
|
59
|
+
#
|
60
|
+
# Will invoke the list task.
|
61
|
+
#
|
62
|
+
# ==== Parameters
|
63
|
+
# Hash[String|Array => Symbol]:: Maps the string or the strings in the array to the given task.
|
64
|
+
#
|
65
|
+
def map(mappings=nil)
|
66
|
+
@map ||= from_superclass(:map, {})
|
67
|
+
|
68
|
+
if mappings
|
69
|
+
mappings.each do |key, value|
|
70
|
+
if key.respond_to?(:each)
|
71
|
+
key.each {|subkey| @map[subkey] = value}
|
72
|
+
else
|
73
|
+
@map[key] = value
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
@map
|
79
|
+
end
|
80
|
+
|
81
|
+
# Declares the options for the next task to be declared.
|
82
|
+
#
|
83
|
+
# ==== Parameters
|
84
|
+
# Hash[Symbol => Object]:: The hash key is the name of the option and the value
|
85
|
+
# is the type of the option. Can be :string, :array, :hash, :boolean, :numeric
|
86
|
+
# or :required (string). If you give a value, the type of the value is used.
|
87
|
+
#
|
88
|
+
def method_options(options=nil)
|
89
|
+
@method_options ||= {}
|
90
|
+
build_options(options, @method_options) if options
|
91
|
+
@method_options
|
92
|
+
end
|
93
|
+
|
94
|
+
# Adds an option to the set of method options. If :for is given as option,
|
95
|
+
# it allows you to change the options from a previous defined task.
|
96
|
+
#
|
97
|
+
# def previous_task
|
98
|
+
# # magic
|
99
|
+
# end
|
100
|
+
#
|
101
|
+
# method_option :foo => :bar, :for => :previous_task
|
102
|
+
#
|
103
|
+
# def next_task
|
104
|
+
# # magic
|
105
|
+
# end
|
106
|
+
#
|
107
|
+
# ==== Parameters
|
108
|
+
# name<Symbol>:: The name of the argument.
|
109
|
+
# options<Hash>:: Described below.
|
110
|
+
#
|
111
|
+
# ==== Options
|
112
|
+
# :desc - Description for the argument.
|
113
|
+
# :required - If the argument is required or not.
|
114
|
+
# :default - Default value for this argument. It cannot be required and have default values.
|
115
|
+
# :aliases - Aliases for this option.
|
116
|
+
# :type - The type of the argument, can be :string, :hash, :array, :numeric or :boolean.
|
117
|
+
# :banner - String to show on usage notes.
|
118
|
+
#
|
119
|
+
def method_option(name, options={})
|
120
|
+
scope = if options[:for]
|
121
|
+
find_and_refresh_task(options[:for]).options
|
122
|
+
else
|
123
|
+
method_options
|
124
|
+
end
|
125
|
+
|
126
|
+
build_option(name, options, scope)
|
127
|
+
end
|
128
|
+
|
129
|
+
# Parses the task and options from the given args, instantiate the class
|
130
|
+
# and invoke the task. This method is used when the arguments must be parsed
|
131
|
+
# from an array. If you are inside Ruby and want to use a Thor class, you
|
132
|
+
# can simply initialize it:
|
133
|
+
#
|
134
|
+
# script = MyScript.new(args, options, config)
|
135
|
+
# script.invoke(:task, first_arg, second_arg, third_arg)
|
136
|
+
#
|
137
|
+
def start(original_args=ARGV, config={})
|
138
|
+
super do |given_args|
|
139
|
+
meth = given_args.first.to_s
|
140
|
+
|
141
|
+
if !meth.empty? && (map[meth] || meth !~ /^\-/)
|
142
|
+
given_args.shift
|
143
|
+
else
|
144
|
+
meth = nil
|
145
|
+
end
|
146
|
+
|
147
|
+
meth = normalize_task_name(meth)
|
148
|
+
task = all_tasks[meth]
|
149
|
+
|
150
|
+
if task
|
151
|
+
args, opts = Thor::Options.split(given_args)
|
152
|
+
config.merge!(:task_options => task.options)
|
153
|
+
else
|
154
|
+
args, opts = given_args, {}
|
155
|
+
end
|
156
|
+
|
157
|
+
task ||= Thor::Task::Dynamic.new(meth)
|
158
|
+
trailing = args[Range.new(arguments.size, -1)]
|
159
|
+
new(args, opts, config).invoke(task, trailing || [])
|
160
|
+
end
|
161
|
+
end
|
162
|
+
|
163
|
+
# Prints help information for the given task.
|
164
|
+
#
|
165
|
+
# ==== Parameters
|
166
|
+
# shell<Thor::Shell>
|
167
|
+
# task_name<String>
|
168
|
+
#
|
169
|
+
def task_help(shell, task_name)
|
170
|
+
meth = normalize_task_name(task_name)
|
171
|
+
task = all_tasks[meth]
|
172
|
+
handle_no_task_error(meth) unless task
|
173
|
+
|
174
|
+
shell.say "Usage:"
|
175
|
+
shell.say " #{banner(task, true)}"
|
176
|
+
shell.say
|
177
|
+
class_options_help(shell, nil => task.options.map { |_, o| o })
|
178
|
+
if task.long_description
|
179
|
+
shell.say "Description:"
|
180
|
+
shell.print_wrapped(task.long_description, :ident => 2)
|
181
|
+
else
|
182
|
+
shell.say task.description
|
183
|
+
end
|
184
|
+
end
|
185
|
+
|
186
|
+
# Prints help information for this class.
|
187
|
+
#
|
188
|
+
# ==== Parameters
|
189
|
+
# shell<Thor::Shell>
|
190
|
+
#
|
191
|
+
def help(shell)
|
192
|
+
list = printable_tasks
|
193
|
+
Thor::Util.thor_classes_in(self).each do |klass|
|
194
|
+
list += klass.printable_tasks(false)
|
195
|
+
end
|
196
|
+
list.sort!{ |a,b| a[0] <=> b[0] }
|
197
|
+
|
198
|
+
shell.say "Tasks:"
|
199
|
+
shell.print_table(list, :ident => 2, :truncate => true)
|
200
|
+
shell.say
|
201
|
+
class_options_help(shell)
|
202
|
+
end
|
203
|
+
|
204
|
+
# Returns tasks ready to be printed.
|
205
|
+
def printable_tasks(all=true)
|
206
|
+
(all ? all_tasks : tasks).map do |_, task|
|
207
|
+
item = []
|
208
|
+
item << banner(task)
|
209
|
+
item << (task.description ? "# #{task.description.gsub(/\s+/m,' ')}" : "")
|
210
|
+
item
|
211
|
+
end
|
212
|
+
end
|
213
|
+
|
214
|
+
def handle_argument_error(task, error) #:nodoc:
|
215
|
+
raise InvocationError, "#{task.name.inspect} was called incorrectly. Call as #{task.formatted_usage(self, banner_base == "thor").inspect}."
|
216
|
+
end
|
217
|
+
|
218
|
+
protected
|
219
|
+
|
220
|
+
# The banner for this class. You can customize it if you are invoking the
|
221
|
+
# thor class by another ways which is not the Thor::Runner. It receives
|
222
|
+
# the task that is going to be invoked and a boolean which indicates if
|
223
|
+
# the namespace should be displayed as arguments.
|
224
|
+
#
|
225
|
+
def banner(task, task_help = false)
|
226
|
+
"#{banner_base} #{task.formatted_usage(self, banner_base == "thor")}"
|
227
|
+
end
|
228
|
+
|
229
|
+
def baseclass #:nodoc:
|
230
|
+
Thor
|
231
|
+
end
|
232
|
+
|
233
|
+
def create_task(meth) #:nodoc:
|
234
|
+
if @usage && @desc
|
235
|
+
tasks[meth.to_s] = Thor::Task.new(meth, @desc, @long_desc, @usage, method_options)
|
236
|
+
@usage, @desc, @long_desc, @method_options = nil
|
237
|
+
true
|
238
|
+
elsif self.all_tasks[meth.to_s] || meth.to_sym == :method_missing
|
239
|
+
true
|
240
|
+
else
|
241
|
+
puts "[WARNING] Attempted to create task #{meth.inspect} without usage or description. " <<
|
242
|
+
"Call desc if you want this method to be available as task or declare it inside a " <<
|
243
|
+
"no_tasks{} block. Invoked from #{caller[1].inspect}."
|
244
|
+
false
|
245
|
+
end
|
246
|
+
end
|
247
|
+
|
248
|
+
def initialize_added #:nodoc:
|
249
|
+
class_options.merge!(method_options)
|
250
|
+
@method_options = nil
|
251
|
+
end
|
252
|
+
|
253
|
+
# Receives a task name (can be nil), and try to get a map from it.
|
254
|
+
# If a map can't be found use the sent name or the default task.
|
255
|
+
#
|
256
|
+
def normalize_task_name(meth) #:nodoc:
|
257
|
+
meth = map[meth.to_s] || meth || default_task
|
258
|
+
meth.to_s.gsub('-','_') # treat foo-bar > foo_bar
|
259
|
+
end
|
260
|
+
end
|
261
|
+
|
262
|
+
include Thor::Base
|
263
|
+
|
264
|
+
map HELP_MAPPINGS => :help
|
265
|
+
|
266
|
+
desc "help [TASK]", "Describe available tasks or one specific task"
|
267
|
+
def help(task=nil)
|
268
|
+
task ? self.class.task_help(shell, task) : self.class.help(shell)
|
269
|
+
end
|
270
|
+
end
|