capistrano 1.4.2 → 2.0.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.
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