capistrano 1.4.2 → 2.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (113) hide show
  1. data/CHANGELOG +140 -4
  2. data/MIT-LICENSE +1 -1
  3. data/README +22 -14
  4. data/bin/cap +1 -8
  5. data/bin/capify +77 -0
  6. data/examples/sample.rb +10 -109
  7. data/lib/capistrano.rb +1 -0
  8. data/lib/capistrano/callback.rb +41 -0
  9. data/lib/capistrano/cli.rb +17 -317
  10. data/lib/capistrano/cli/execute.rb +82 -0
  11. data/lib/capistrano/cli/help.rb +102 -0
  12. data/lib/capistrano/cli/help.txt +53 -0
  13. data/lib/capistrano/cli/options.rb +183 -0
  14. data/lib/capistrano/cli/ui.rb +28 -0
  15. data/lib/capistrano/command.rb +62 -29
  16. data/lib/capistrano/configuration.rb +25 -226
  17. data/lib/capistrano/configuration/actions/file_transfer.rb +35 -0
  18. data/lib/capistrano/configuration/actions/inspect.rb +46 -0
  19. data/lib/capistrano/configuration/actions/invocation.rb +127 -0
  20. data/lib/capistrano/configuration/callbacks.rb +148 -0
  21. data/lib/capistrano/configuration/connections.rb +159 -0
  22. data/lib/capistrano/configuration/execution.rb +126 -0
  23. data/lib/capistrano/configuration/loading.rb +112 -0
  24. data/lib/capistrano/configuration/namespaces.rb +190 -0
  25. data/lib/capistrano/configuration/roles.rb +51 -0
  26. data/lib/capistrano/configuration/servers.rb +75 -0
  27. data/lib/capistrano/configuration/variables.rb +127 -0
  28. data/lib/capistrano/errors.rb +15 -0
  29. data/lib/capistrano/extensions.rb +27 -8
  30. data/lib/capistrano/gateway.rb +54 -29
  31. data/lib/capistrano/logger.rb +11 -11
  32. data/lib/capistrano/recipes/compat.rb +32 -0
  33. data/lib/capistrano/recipes/deploy.rb +483 -0
  34. data/lib/capistrano/recipes/deploy/dependencies.rb +44 -0
  35. data/lib/capistrano/recipes/deploy/local_dependency.rb +46 -0
  36. data/lib/capistrano/recipes/deploy/remote_dependency.rb +65 -0
  37. data/lib/capistrano/recipes/deploy/scm.rb +19 -0
  38. data/lib/capistrano/recipes/deploy/scm/base.rb +180 -0
  39. data/lib/capistrano/recipes/deploy/scm/bzr.rb +86 -0
  40. data/lib/capistrano/recipes/deploy/scm/cvs.rb +151 -0
  41. data/lib/capistrano/recipes/deploy/scm/darcs.rb +85 -0
  42. data/lib/capistrano/recipes/deploy/scm/mercurial.rb +129 -0
  43. data/lib/capistrano/recipes/deploy/scm/perforce.rb +126 -0
  44. data/lib/capistrano/recipes/deploy/scm/subversion.rb +103 -0
  45. data/lib/capistrano/recipes/deploy/strategy.rb +19 -0
  46. data/lib/capistrano/recipes/deploy/strategy/base.rb +64 -0
  47. data/lib/capistrano/recipes/deploy/strategy/checkout.rb +20 -0
  48. data/lib/capistrano/recipes/deploy/strategy/copy.rb +143 -0
  49. data/lib/capistrano/recipes/deploy/strategy/export.rb +20 -0
  50. data/lib/capistrano/recipes/deploy/strategy/remote.rb +52 -0
  51. data/lib/capistrano/recipes/deploy/strategy/remote_cache.rb +47 -0
  52. data/lib/capistrano/recipes/deploy/templates/maintenance.rhtml +53 -0
  53. data/lib/capistrano/recipes/standard.rb +26 -276
  54. data/lib/capistrano/recipes/templates/maintenance.rhtml +1 -1
  55. data/lib/capistrano/recipes/upgrade.rb +33 -0
  56. data/lib/capistrano/server_definition.rb +51 -0
  57. data/lib/capistrano/shell.rb +125 -81
  58. data/lib/capistrano/ssh.rb +80 -36
  59. data/lib/capistrano/task_definition.rb +69 -0
  60. data/lib/capistrano/upload.rb +146 -0
  61. data/lib/capistrano/version.rb +13 -17
  62. data/test/cli/execute_test.rb +132 -0
  63. data/test/cli/help_test.rb +139 -0
  64. data/test/cli/options_test.rb +226 -0
  65. data/test/cli/ui_test.rb +28 -0
  66. data/test/cli_test.rb +17 -0
  67. data/test/command_test.rb +284 -25
  68. data/test/configuration/actions/file_transfer_test.rb +40 -0
  69. data/test/configuration/actions/inspect_test.rb +62 -0
  70. data/test/configuration/actions/invocation_test.rb +195 -0
  71. data/test/configuration/callbacks_test.rb +206 -0
  72. data/test/configuration/connections_test.rb +288 -0
  73. data/test/configuration/execution_test.rb +159 -0
  74. data/test/configuration/loading_test.rb +119 -0
  75. data/test/configuration/namespace_dsl_test.rb +283 -0
  76. data/test/configuration/roles_test.rb +47 -0
  77. data/test/configuration/servers_test.rb +90 -0
  78. data/test/configuration/variables_test.rb +180 -0
  79. data/test/configuration_test.rb +60 -212
  80. data/test/deploy/scm/base_test.rb +55 -0
  81. data/test/deploy/strategy/copy_test.rb +146 -0
  82. data/test/extensions_test.rb +69 -0
  83. data/test/fixtures/cli_integration.rb +5 -0
  84. data/test/fixtures/custom.rb +2 -2
  85. data/test/gateway_test.rb +167 -0
  86. data/test/logger_test.rb +123 -0
  87. data/test/server_definition_test.rb +108 -0
  88. data/test/shell_test.rb +64 -0
  89. data/test/ssh_test.rb +67 -154
  90. data/test/task_definition_test.rb +101 -0
  91. data/test/upload_test.rb +131 -0
  92. data/test/utils.rb +31 -39
  93. data/test/version_test.rb +24 -0
  94. metadata +145 -98
  95. data/THANKS +0 -4
  96. data/lib/capistrano/actor.rb +0 -567
  97. data/lib/capistrano/generators/rails/deployment/deployment_generator.rb +0 -25
  98. data/lib/capistrano/generators/rails/deployment/templates/capistrano.rake +0 -49
  99. data/lib/capistrano/generators/rails/deployment/templates/deploy.rb +0 -122
  100. data/lib/capistrano/generators/rails/loader.rb +0 -20
  101. data/lib/capistrano/scm/base.rb +0 -61
  102. data/lib/capistrano/scm/baz.rb +0 -118
  103. data/lib/capistrano/scm/bzr.rb +0 -70
  104. data/lib/capistrano/scm/cvs.rb +0 -129
  105. data/lib/capistrano/scm/darcs.rb +0 -27
  106. data/lib/capistrano/scm/mercurial.rb +0 -83
  107. data/lib/capistrano/scm/perforce.rb +0 -139
  108. data/lib/capistrano/scm/subversion.rb +0 -128
  109. data/lib/capistrano/transfer.rb +0 -97
  110. data/lib/capistrano/utils.rb +0 -26
  111. data/test/actor_test.rb +0 -402
  112. data/test/scm/cvs_test.rb +0 -196
  113. data/test/scm/subversion_test.rb +0 -145
@@ -1,83 +1,25 @@
1
- require 'optparse'
2
1
  require 'capistrano'
2
+ require 'capistrano/cli/execute'
3
+ require 'capistrano/cli/help'
4
+ require 'capistrano/cli/options'
5
+ require 'capistrano/cli/ui'
3
6
 
4
7
  module Capistrano
5
8
  # The CLI class encapsulates the behavior of capistrano when it is invoked
6
- # as a command-line utility. This allows other programs to embed ST and
7
- # preserve it's command-line semantics.
9
+ # as a command-line utility. This allows other programs to embed Capistrano
10
+ # and preserve its command-line semantics.
8
11
  class CLI
9
- # Invoke capistrano using the ARGV array as the option parameters. This
10
- # is what the command-line capistrano utility does.
11
- def self.execute!
12
- new.execute!
13
- end
14
-
15
- # The following determines whether or not echo-suppression is available.
16
- # This requires the termios library to be installed (which, unfortunately,
17
- # is not available for Windows).
18
- begin
19
- require 'termios'
20
-
21
- # Enable or disable stdin echoing to the terminal.
22
- def self.echo(enable)
23
- term = Termios::getattr(STDIN)
24
-
25
- if enable
26
- term.c_lflag |= (Termios::ECHO | Termios::ICANON)
27
- else
28
- term.c_lflag &= ~Termios::ECHO
29
- end
30
-
31
- Termios::setattr(STDIN, Termios::TCSANOW, term)
32
- end
33
- rescue LoadError
34
- def self.echo(enable)
35
- end
36
- end
37
-
38
- # execute the associated block with echo-suppression enabled. Note that
39
- # if termios is not available, echo suppression will not be available
40
- # either.
41
- def self.with_echo
42
- unless @warned_about_echo
43
- puts "WARNING: Password will echo -- install the 'termios' gem to hide your password." if !defined?(Termios) && RUBY_PLATFORM !~ /mswin/
44
- @warned_about_echo = true
45
- end
46
- echo(false)
47
- yield
48
- ensure
49
- echo(true)
50
- end
51
-
52
- # Prompt for a password using echo suppression.
53
- def self.password_prompt(prompt="Password: ")
54
- sync = STDOUT.sync
55
- begin
56
- with_echo do
57
- STDOUT.sync = true
58
- print(prompt)
59
- STDIN.gets.chomp
60
- end
61
- ensure
62
- STDOUT.sync = sync
63
- puts
64
- end
65
- end
66
-
67
12
  # The array of (unparsed) command-line options
68
13
  attr_reader :args
69
14
 
70
- # The hash of (parsed) command-line options
71
- attr_reader :options
72
-
73
15
  # Create a new CLI instance using the given array of command-line parameters
74
16
  # to initialize it. By default, +ARGV+ is used, but you can specify a
75
- # different set of parameters (such as when embedded ST in a program):
17
+ # different set of parameters (such as when embedded cap in a program):
76
18
  #
77
19
  # require 'capistrano/cli'
78
- # Capistrano::CLI.new(%w(-vvvv -r config/deploy -a update_code)).execute!
20
+ # Capistrano::CLI.parse(%w(-vvvv -r config/deploy update_code)).execute!
79
21
  #
80
- # Note that you can also embed ST directly by creating a new Configuration
22
+ # Note that you can also embed cap directly by creating a new Configuration
81
23
  # instance and setting it up, but you'll often wind up duplicating logic
82
24
  # defined in the CLI class. The above snippet, redone using the Configuration
83
25
  # class directly, would look like:
@@ -87,261 +29,19 @@ module Capistrano
87
29
  # config = Capistrano::Configuration.new
88
30
  # config.logger_level = Capistrano::Logger::TRACE
89
31
  # config.set(:password) { Capistrano::CLI.password_prompt }
90
- # config.load "standard", "config/deploy"
91
- # config.actor.update_code
32
+ # config.load "config/deploy"
33
+ # config.update_code
92
34
  #
93
35
  # There may be times that you want/need the additional control offered by
94
36
  # manipulating the Configuration directly, but generally interfacing with
95
37
  # the CLI class is recommended.
96
- def initialize(args = ARGV)
97
- @args = args
98
- @options = { :recipes => [], :actions => [], :vars => {},
99
- :pre_vars => {}, :sysconf => default_sysconf, :dotfile => default_dotfile }
100
-
101
- OptionParser.new do |opts|
102
- opts.banner = "Usage: #{$0} [options] [args]"
103
-
104
- opts.separator ""
105
- opts.separator "Recipe Options -----------------------"
106
- opts.separator ""
107
-
108
- opts.on("-a", "--action ACTION",
109
- "An action to execute. Multiple actions may",
110
- "be specified, and are loaded in the given order."
111
- ) { |value| @options[:actions] << value }
112
-
113
- opts.on("-f", "--file FILE",
114
- "A recipe file to load. Multiple recipes may",
115
- "be specified, and are loaded in the given order."
116
- ) { |value| @options[:recipes] << value }
117
-
118
- opts.on("-p", "--password [PASSWORD]",
119
- "The password to use when connecting. If the switch",
120
- "is given without a password, the password will be",
121
- "prompted for immediately. (Default: prompt for password",
122
- "the first time it is needed.)"
123
- ) { |value| @options[:password] = value }
124
-
125
- opts.on("-r", "--recipe RECIPE",
126
- "A recipe file to load. Multiple recipes may",
127
- "be specified, and are loaded in the given order.",
128
- "(This option is deprecated--please use -f instead)"
129
- ) do |value|
130
- warn "Deprecated -r/--recipe flag used. Please use -f instead"
131
- @options[:recipes] << value
132
- end
133
-
134
- opts.on("-s", "--set NAME=VALUE",
135
- "Specify a variable and it's value to set. This",
136
- "will be set after loading all recipe files."
137
- ) do |pair|
138
- name, value = pair.split(/=/, 2)
139
- @options[:vars][name.to_sym] = value
140
- end
141
-
142
- opts.on("-S", "--set-before NAME=VALUE",
143
- "Specify a variable and it's value to set. This",
144
- "will be set BEFORE loading all recipe files."
145
- ) do |pair|
146
- name, value = pair.split(/=/, 2)
147
- @options[:pre_vars][name.to_sym] = value
148
- end
149
-
150
- opts.on("-x", "--skip-config",
151
- "Disables the loading of the default personal config",
152
- "file. Specifying -C after this option will reenable",
153
- "it. (Default: config file is loaded)"
154
- ) { @options[:dotfile] = nil }
155
-
156
- opts.separator ""
157
- opts.separator "Framework Integration Options --------"
158
- opts.separator ""
159
-
160
- opts.on("-A", "--apply-to DIRECTORY",
161
- "Create a minimal set of scripts and recipes to use",
162
- "capistrano with the application at the given",
163
- "directory. (Currently only works with Rails apps.)"
164
- ) { |value| @options[:apply_to] = value }
165
-
166
- opts.separator ""
167
- opts.separator "Miscellaneous Options ----------------"
168
- opts.separator ""
169
-
170
- opts.on("-h", "--help", "Display this help message") do
171
- puts opts
172
- exit
173
- end
174
-
175
- opts.on("-P", "--[no-]pretend",
176
- "Run the task(s), but don't actually connect to or",
177
- "execute anything on the servers. (For various reasons",
178
- "this will not necessarily be an accurate depiction",
179
- "of the work that will actually be performed.",
180
- "Default: don't pretend.)"
181
- ) { |value| @options[:pretend] = value }
182
-
183
- opts.on("-q", "--quiet",
184
- "Make the output as quiet as possible (the default)"
185
- ) { @options[:verbose] = 0 }
186
-
187
- opts.on("-v", "--verbose",
188
- "Specify the verbosity of the output.",
189
- "May be given multiple times. (Default: silent)"
190
- ) { @options[:verbose] ||= 0; @options[:verbose] += 1 }
191
-
192
- opts.on("-V", "--version",
193
- "Display the version info for this utility"
194
- ) do
195
- require 'capistrano/version'
196
- puts "Capistrano v#{Capistrano::Version::STRING}"
197
- exit
198
- end
199
-
200
- opts.separator ""
201
- opts.separator <<-DETAIL.split(/\n/)
202
- You can use the --apply-to switch to generate a minimal set of capistrano
203
- scripts and recipes for an application. Just specify the path to the application
204
- as the argument to --apply-to, like this:
205
-
206
- cap --apply-to ~/projects/myapp
207
-
208
- You'll wind up with a sample deployment recipe in config/deploy.rb and some new
209
- rake tasks in lib/tasks.
210
-
211
- (Currently, --apply-to only works with Rails applications.)
212
- DETAIL
213
- #' # vim syntax highlighting fix
214
-
215
- if args.empty?
216
- puts opts
217
- exit
218
- else
219
- opts.parse!(args)
220
- end
221
- end
222
-
223
- check_options!
224
-
225
- password_proc = Proc.new { self.class.password_prompt }
226
-
227
- if !@options.has_key?(:password)
228
- @options[:password] = password_proc
229
- elsif !@options[:password]
230
- @options[:password] = password_proc.call
231
- end
232
- end
233
-
234
- # Beginning running Capistrano based on the configured options.
235
- def execute!
236
- if @options[:apply_to]
237
- execute_apply_to!
238
- else
239
- execute_recipes!
240
- end
38
+ def initialize(args)
39
+ @args = args.dup
40
+ $stdout.sync = true # so that Net::SSH prompts show up
241
41
  end
242
42
 
243
- private
244
-
245
- # Load the recipes specified by the options, and execute the actions
246
- # specified.
247
- def execute_recipes!
248
- config = Capistrano::Configuration.new
249
- config.logger.level = options[:verbose]
250
- config.set :password, options[:password]
251
- config.set :pretend, options[:pretend]
252
-
253
- options[:pre_vars].each { |name, value| config.set(name, value) }
254
-
255
- # load the standard recipe definition
256
- config.load "standard"
257
-
258
- # load systemwide config/recipe definition
259
- config.load(@options[:sysconf]) if @options[:sysconf] && File.exist?(@options[:sysconf])
260
-
261
- # load user config/recipe definition
262
- config.load(@options[:dotfile]) if @options[:dotfile] && File.exist?(@options[:dotfile])
263
-
264
- options[:recipes].each { |recipe| config.load(recipe) }
265
- options[:vars].each { |name, value| config.set(name, value) }
266
-
267
- actor = config.actor
268
- options[:actions].each { |action| actor.send action }
269
- rescue Exception => error
270
- handle_error(error)
271
- end
272
-
273
- # Load the Rails generator and apply it to the specified directory.
274
- def execute_apply_to!
275
- require 'capistrano/generators/rails/loader'
276
- Generators::RailsLoader.load! @options
277
- end
278
-
279
- APPLY_TO_OPTIONS = [:apply_to]
280
- RECIPE_OPTIONS = [:password]
281
- DEFAULT_RECIPES = %w(Capfile capfile config/deploy.rb)
282
-
283
- # A sanity check to ensure that a valid operation is specified.
284
- def check_options!
285
- # if no verbosity has been specified, be verbose
286
- @options[:verbose] = 3 if !@options.has_key?(:verbose)
287
-
288
- apply_to_given = !(@options.keys & APPLY_TO_OPTIONS).empty?
289
- recipe_given = !(@options.keys & RECIPE_OPTIONS).empty? ||
290
- !@options[:recipes].empty? ||
291
- !@options[:actions].empty?
292
-
293
- if apply_to_given && recipe_given
294
- abort "You cannot specify both recipe options and framework integration options."
295
- elsif !apply_to_given
296
- look_for_default_recipe_file! if @options[:recipes].empty?
297
- look_for_raw_actions!
298
- abort "You must specify at least one action" if @options[:actions].empty?
299
- else
300
- @options[:application] = args.shift
301
- @options[:recipe_file] = args.shift
302
- end
303
- end
304
-
305
- def default_sysconf
306
- File.join(sysconf_directory, "capistrano.conf")
307
- end
308
-
309
- def default_dotfile
310
- File.join(home_directory, ".caprc")
311
- end
312
-
313
- def sysconf_directory
314
- # I'm guessing at where Windows users would keep their conf file.
315
- ENV["SystemRoot"] || '/etc'
316
- end
317
-
318
- def home_directory
319
- ENV["HOME"] ||
320
- (ENV["HOMEPATH"] && "#{ENV["HOMEDRIVE"]}#{ENV["HOMEPATH"]}") ||
321
- "/"
322
- end
323
-
324
- def look_for_default_recipe_file!
325
- DEFAULT_RECIPES.each do |file|
326
- if File.exist?(file)
327
- @options[:recipes] << file
328
- break
329
- end
330
- end
331
- end
332
-
333
- def look_for_raw_actions!
334
- @options[:actions].concat(@args)
335
- end
336
-
337
- def handle_error(error)
338
- case error
339
- when Net::SSH::AuthenticationFailed
340
- abort "authentication failed for `#{error.message}'"
341
- when Capistrano::Command::Error
342
- abort(error.message)
343
- else raise error
344
- end
345
- end
43
+ # Mix-in the actual behavior
44
+ include Execute, Options, UI
45
+ include Help # needs to be included last, because it overrides some methods
346
46
  end
347
47
  end
@@ -0,0 +1,82 @@
1
+ require 'capistrano/configuration'
2
+
3
+ module Capistrano
4
+ class CLI
5
+ module Execute
6
+ def self.included(base) #:nodoc:
7
+ base.extend(ClassMethods)
8
+ end
9
+
10
+ module ClassMethods
11
+ # Invoke capistrano using the ARGV array as the option parameters. This
12
+ # is what the command-line capistrano utility does.
13
+ def execute
14
+ parse(ARGV).execute!
15
+ end
16
+ end
17
+
18
+ # Using the options build when the command-line was parsed, instantiate
19
+ # a new Capistrano configuration, initialize it, and execute the
20
+ # requested actions.
21
+ #
22
+ # Returns the Configuration instance used, if successful.
23
+ def execute!
24
+ config = instantiate_configuration
25
+ config.logger.level = options[:verbose]
26
+
27
+ set_pre_vars(config)
28
+ load_recipes(config)
29
+
30
+ config.trigger(:load)
31
+ execute_requested_actions(config)
32
+ config.trigger(:exit)
33
+
34
+ config
35
+ rescue Exception => error
36
+ handle_error(error)
37
+ end
38
+
39
+ def execute_requested_actions(config)
40
+ Array(options[:vars]).each { |name, value| config.set(name, value) }
41
+
42
+ Array(options[:actions]).each do |action|
43
+ config.find_and_execute_task(action, :before => :start, :after => :finish)
44
+ end
45
+ end
46
+
47
+ def set_pre_vars(config) #:nodoc:
48
+ config.set :password, options[:password]
49
+ Array(options[:pre_vars]).each { |name, value| config.set(name, value) }
50
+ end
51
+
52
+ def load_recipes(config) #:nodoc:
53
+ # load the standard recipe definition
54
+ config.load "standard"
55
+
56
+ # load systemwide config/recipe definition
57
+ config.load(options[:sysconf]) if options[:sysconf] && File.file?(options[:sysconf])
58
+
59
+ # load user config/recipe definition
60
+ config.load(options[:dotfile]) if options[:dotfile] && File.file?(options[:dotfile])
61
+
62
+ Array(options[:recipes]).each { |recipe| config.load(recipe) }
63
+ end
64
+
65
+ # Primarily useful for testing, but subclasses of CLI could conceivably
66
+ # override this method to return a Configuration subclass or replacement.
67
+ def instantiate_configuration #:nodoc:
68
+ Capistrano::Configuration.new
69
+ end
70
+
71
+ def handle_error(error) #:nodoc:
72
+ case error
73
+ when Net::SSH::AuthenticationFailed
74
+ abort "authentication failed for `#{error.message}'"
75
+ when Capistrano::Error
76
+ abort(error.message)
77
+ else raise error
78
+ end
79
+ end
80
+ end
81
+ end
82
+ end
@@ -0,0 +1,102 @@
1
+ module Capistrano
2
+ class CLI
3
+ module Help
4
+ LINE_PADDING = 7
5
+ MIN_MAX_LEN = 30
6
+ HEADER_LEN = 60
7
+
8
+ def self.included(base) #:nodoc:
9
+ base.send :alias_method, :execute_requested_actions_without_help, :execute_requested_actions
10
+ base.send :alias_method, :execute_requested_actions, :execute_requested_actions_with_help
11
+ end
12
+
13
+ def execute_requested_actions_with_help(config)
14
+ if options[:tasks]
15
+ task_list(config)
16
+ elsif options[:explain]
17
+ explain_task(config, options[:explain])
18
+ else
19
+ execute_requested_actions_without_help(config)
20
+ end
21
+ end
22
+
23
+ def task_list(config) #:nodoc:
24
+ tasks = config.task_list(:all)
25
+
26
+ if tasks.empty?
27
+ warn "There are no tasks available. Please specify a recipe file to load."
28
+ else
29
+ all_tasks_length = tasks.length
30
+ if options[:verbose].to_i < 1
31
+ tasks = tasks.reject { |t| t.description.empty? || t.description =~ /^\[internal\]/ }
32
+ end
33
+
34
+ tasks = tasks.sort_by { |task| task.fully_qualified_name }
35
+
36
+ longest = tasks.map { |task| task.fully_qualified_name.length }.max
37
+ max_length = output_columns - longest - LINE_PADDING
38
+ max_length = MIN_MAX_LEN if max_length < MIN_MAX_LEN
39
+
40
+ tasks.each do |task|
41
+ puts "cap %-#{longest}s # %s" % [task.fully_qualified_name, task.brief_description(max_length)]
42
+ end
43
+
44
+ if all_tasks_length > tasks.length
45
+ puts
46
+ puts "Some tasks were not listed, either because they have no description,"
47
+ puts "or because they are only used internally by other tasks. To see all"
48
+ puts "tasks, type `#{File.basename($0)} -Tv'."
49
+ end
50
+
51
+ puts
52
+ puts "Extended help may be available for these tasks."
53
+ puts "Type `#{File.basename($0)} -e taskname' to view it."
54
+ end
55
+ end
56
+
57
+ def explain_task(config, name) #:nodoc:
58
+ task = config.find_task(name)
59
+ if task.nil?
60
+ warn "The task `#{name}' does not exist."
61
+ else
62
+ puts "-" * HEADER_LEN
63
+ puts "cap #{name}"
64
+ puts "-" * HEADER_LEN
65
+
66
+ if task.description.empty?
67
+ puts "There is no description for this task."
68
+ else
69
+ puts format_text(task.description)
70
+ end
71
+
72
+ puts
73
+ end
74
+ end
75
+
76
+ def long_help #:nodoc:
77
+ help_text = File.read(File.join(File.dirname(__FILE__), "help.txt"))
78
+ self.class.ui.page_at = self.class.ui.output_rows - 2
79
+ self.class.ui.say format_text(help_text)
80
+ end
81
+
82
+ def format_text(text) #:nodoc:
83
+ formatted = ""
84
+ text.each_line do |line|
85
+ indentation = line[/^\s+/] || ""
86
+ indentation_size = indentation.split(//).inject(0) { |c,s| c + (s[0] == ?\t ? 8 : 1) }
87
+ lines = line.strip.gsub(/(.{1,#{output_columns - indentation_size}})(?:\s+|\Z)/, "\\1\n").split(/\n/)
88
+ if lines.empty?
89
+ formatted << "\n"
90
+ else
91
+ formatted << lines.map { |l| "#{indentation}#{l}\n" }.join
92
+ end
93
+ end
94
+ formatted
95
+ end
96
+
97
+ def output_columns #:nodoc:
98
+ @output_columns ||= self.class.ui.output_cols > 80 ? 80 : self.class.ui.output_cols
99
+ end
100
+ end
101
+ end
102
+ end