engineyard 0.5.3 → 0.5.4
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.
- 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
|