capistrano-edge 2.5.6

Sign up to get free protection for your applications and to get access to all the features.
Files changed (109) hide show
  1. data/CHANGELOG.rdoc +770 -0
  2. data/Manifest +104 -0
  3. data/README.rdoc +66 -0
  4. data/Rakefile +35 -0
  5. data/bin/cap +4 -0
  6. data/bin/capify +95 -0
  7. data/capistrano.gemspec +51 -0
  8. data/examples/sample.rb +14 -0
  9. data/lib/capistrano.rb +2 -0
  10. data/lib/capistrano/callback.rb +45 -0
  11. data/lib/capistrano/cli.rb +47 -0
  12. data/lib/capistrano/cli/execute.rb +84 -0
  13. data/lib/capistrano/cli/help.rb +125 -0
  14. data/lib/capistrano/cli/help.txt +75 -0
  15. data/lib/capistrano/cli/options.rb +224 -0
  16. data/lib/capistrano/cli/ui.rb +40 -0
  17. data/lib/capistrano/command.rb +283 -0
  18. data/lib/capistrano/configuration.rb +43 -0
  19. data/lib/capistrano/configuration/actions/file_transfer.rb +47 -0
  20. data/lib/capistrano/configuration/actions/inspect.rb +46 -0
  21. data/lib/capistrano/configuration/actions/invocation.rb +293 -0
  22. data/lib/capistrano/configuration/callbacks.rb +148 -0
  23. data/lib/capistrano/configuration/connections.rb +204 -0
  24. data/lib/capistrano/configuration/execution.rb +143 -0
  25. data/lib/capistrano/configuration/loading.rb +197 -0
  26. data/lib/capistrano/configuration/namespaces.rb +197 -0
  27. data/lib/capistrano/configuration/roles.rb +73 -0
  28. data/lib/capistrano/configuration/servers.rb +85 -0
  29. data/lib/capistrano/configuration/variables.rb +127 -0
  30. data/lib/capistrano/errors.rb +15 -0
  31. data/lib/capistrano/extensions.rb +57 -0
  32. data/lib/capistrano/logger.rb +59 -0
  33. data/lib/capistrano/processable.rb +53 -0
  34. data/lib/capistrano/recipes/compat.rb +32 -0
  35. data/lib/capistrano/recipes/deploy.rb +438 -0
  36. data/lib/capistrano/recipes/deploy/dependencies.rb +44 -0
  37. data/lib/capistrano/recipes/deploy/local_dependency.rb +54 -0
  38. data/lib/capistrano/recipes/deploy/remote_dependency.rb +105 -0
  39. data/lib/capistrano/recipes/deploy/scm.rb +19 -0
  40. data/lib/capistrano/recipes/deploy/scm/accurev.rb +169 -0
  41. data/lib/capistrano/recipes/deploy/scm/base.rb +196 -0
  42. data/lib/capistrano/recipes/deploy/scm/bzr.rb +83 -0
  43. data/lib/capistrano/recipes/deploy/scm/cvs.rb +152 -0
  44. data/lib/capistrano/recipes/deploy/scm/darcs.rb +85 -0
  45. data/lib/capistrano/recipes/deploy/scm/git.rb +274 -0
  46. data/lib/capistrano/recipes/deploy/scm/mercurial.rb +137 -0
  47. data/lib/capistrano/recipes/deploy/scm/none.rb +44 -0
  48. data/lib/capistrano/recipes/deploy/scm/perforce.rb +138 -0
  49. data/lib/capistrano/recipes/deploy/scm/subversion.rb +121 -0
  50. data/lib/capistrano/recipes/deploy/strategy.rb +19 -0
  51. data/lib/capistrano/recipes/deploy/strategy/base.rb +79 -0
  52. data/lib/capistrano/recipes/deploy/strategy/checkout.rb +20 -0
  53. data/lib/capistrano/recipes/deploy/strategy/copy.rb +210 -0
  54. data/lib/capistrano/recipes/deploy/strategy/export.rb +20 -0
  55. data/lib/capistrano/recipes/deploy/strategy/remote.rb +52 -0
  56. data/lib/capistrano/recipes/deploy/strategy/remote_cache.rb +56 -0
  57. data/lib/capistrano/recipes/deploy/templates/maintenance.rhtml +53 -0
  58. data/lib/capistrano/recipes/ext/rails-database-migrations.rb +50 -0
  59. data/lib/capistrano/recipes/ext/web-disable-enable.rb +40 -0
  60. data/lib/capistrano/recipes/standard.rb +37 -0
  61. data/lib/capistrano/recipes/templates/maintenance.rhtml +53 -0
  62. data/lib/capistrano/recipes/upgrade.rb +33 -0
  63. data/lib/capistrano/role.rb +102 -0
  64. data/lib/capistrano/server_definition.rb +56 -0
  65. data/lib/capistrano/shell.rb +260 -0
  66. data/lib/capistrano/ssh.rb +99 -0
  67. data/lib/capistrano/task_definition.rb +70 -0
  68. data/lib/capistrano/transfer.rb +216 -0
  69. data/lib/capistrano/version.rb +18 -0
  70. data/setup.rb +1346 -0
  71. data/test/cli/execute_test.rb +132 -0
  72. data/test/cli/help_test.rb +165 -0
  73. data/test/cli/options_test.rb +317 -0
  74. data/test/cli/ui_test.rb +28 -0
  75. data/test/cli_test.rb +17 -0
  76. data/test/command_test.rb +286 -0
  77. data/test/configuration/actions/file_transfer_test.rb +61 -0
  78. data/test/configuration/actions/inspect_test.rb +65 -0
  79. data/test/configuration/actions/invocation_test.rb +224 -0
  80. data/test/configuration/callbacks_test.rb +220 -0
  81. data/test/configuration/connections_test.rb +349 -0
  82. data/test/configuration/execution_test.rb +175 -0
  83. data/test/configuration/loading_test.rb +132 -0
  84. data/test/configuration/namespace_dsl_test.rb +311 -0
  85. data/test/configuration/roles_test.rb +144 -0
  86. data/test/configuration/servers_test.rb +121 -0
  87. data/test/configuration/variables_test.rb +184 -0
  88. data/test/configuration_test.rb +88 -0
  89. data/test/deploy/local_dependency_test.rb +76 -0
  90. data/test/deploy/remote_dependency_test.rb +114 -0
  91. data/test/deploy/scm/accurev_test.rb +23 -0
  92. data/test/deploy/scm/base_test.rb +55 -0
  93. data/test/deploy/scm/git_test.rb +184 -0
  94. data/test/deploy/scm/mercurial_test.rb +129 -0
  95. data/test/deploy/scm/none_test.rb +35 -0
  96. data/test/deploy/strategy/copy_test.rb +258 -0
  97. data/test/extensions_test.rb +69 -0
  98. data/test/fixtures/cli_integration.rb +5 -0
  99. data/test/fixtures/config.rb +5 -0
  100. data/test/fixtures/custom.rb +3 -0
  101. data/test/logger_test.rb +123 -0
  102. data/test/role_test.rb +11 -0
  103. data/test/server_definition_test.rb +121 -0
  104. data/test/shell_test.rb +90 -0
  105. data/test/ssh_test.rb +104 -0
  106. data/test/task_definition_test.rb +101 -0
  107. data/test/transfer_test.rb +160 -0
  108. data/test/utils.rb +38 -0
  109. metadata +321 -0
@@ -0,0 +1,143 @@
1
+ require 'capistrano/errors'
2
+
3
+ module Capistrano
4
+ class Configuration
5
+ module Execution
6
+ def self.included(base) #:nodoc:
7
+ base.send :alias_method, :initialize_without_execution, :initialize
8
+ base.send :alias_method, :initialize, :initialize_with_execution
9
+ end
10
+
11
+ # A struct for representing a single instance of an invoked task.
12
+ TaskCallFrame = Struct.new(:task, :rollback)
13
+
14
+ def initialize_with_execution(*args) #:nodoc:
15
+ initialize_without_execution(*args)
16
+ end
17
+ private :initialize_with_execution
18
+
19
+ # Returns true if there is a transaction currently active.
20
+ def transaction?
21
+ !rollback_requests.nil?
22
+ end
23
+
24
+ # The call stack of the tasks. The currently executing task may inspect
25
+ # this to see who its caller was. The current task is always the last
26
+ # element of this stack.
27
+ def task_call_frames
28
+ Thread.current[:task_call_frames] ||= []
29
+ end
30
+
31
+
32
+ # The stack of tasks that have registered rollback handlers within the
33
+ # current transaction. If this is nil, then there is no transaction
34
+ # that is currently active.
35
+ def rollback_requests
36
+ Thread.current[:rollback_requests]
37
+ end
38
+
39
+ def rollback_requests=(rollback_requests)
40
+ Thread.current[:rollback_requests] = rollback_requests
41
+ end
42
+
43
+ # Invoke a set of tasks in a transaction. If any task fails (raises an
44
+ # exception), all tasks executed within the transaction are inspected to
45
+ # see if they have an associated on_rollback hook, and if so, that hook
46
+ # is called.
47
+ def transaction
48
+ raise ArgumentError, "expected a block" unless block_given?
49
+ raise ScriptError, "transaction must be called from within a task" if task_call_frames.empty?
50
+
51
+ return yield if transaction?
52
+
53
+ logger.info "transaction: start"
54
+ begin
55
+ self.rollback_requests = []
56
+ yield
57
+ logger.info "transaction: commit"
58
+ rescue Object => e
59
+ rollback!
60
+ raise
61
+ ensure
62
+ self.rollback_requests = nil if Thread.main == Thread.current
63
+ end
64
+ end
65
+
66
+ # Specifies an on_rollback hook for the currently executing task. If this
67
+ # or any subsequent task then fails, and a transaction is active, this
68
+ # hook will be executed.
69
+ def on_rollback(&block)
70
+ if transaction?
71
+ # don't note a new rollback request if one has already been set
72
+ rollback_requests << task_call_frames.last unless task_call_frames.last.rollback
73
+ task_call_frames.last.rollback = block
74
+ end
75
+ end
76
+
77
+ # Returns the TaskDefinition object for the currently executing task.
78
+ # It returns nil if there is no task being executed.
79
+ def current_task
80
+ return nil if task_call_frames.empty?
81
+ task_call_frames.last.task
82
+ end
83
+
84
+ # Executes the task with the given name, without invoking any associated
85
+ # callbacks.
86
+ def execute_task(task)
87
+ logger.debug "executing `#{task.fully_qualified_name}'"
88
+ push_task_call_frame(task)
89
+ invoke_task_directly(task)
90
+ ensure
91
+ pop_task_call_frame
92
+ end
93
+
94
+ # Attempts to locate the task at the given fully-qualified path, and
95
+ # execute it. If no such task exists, a Capistrano::NoSuchTaskError will
96
+ # be raised.
97
+ def find_and_execute_task(path, hooks={})
98
+ task = find_task(path) or raise NoSuchTaskError, "the task `#{path}' does not exist"
99
+
100
+ trigger(hooks[:before], task) if hooks[:before]
101
+ result = execute_task(task)
102
+ trigger(hooks[:after], task) if hooks[:after]
103
+
104
+ result
105
+ end
106
+
107
+ protected
108
+
109
+ def rollback!
110
+ return if Thread.current[:rollback_requests].nil?
111
+ Thread.current[:rolled_back] = true
112
+
113
+ # throw the task back on the stack so that roles are properly
114
+ # interpreted in the scope of the task in question.
115
+ rollback_requests.reverse.each do |frame|
116
+ begin
117
+ push_task_call_frame(frame.task)
118
+ logger.important "rolling back", frame.task.fully_qualified_name
119
+ frame.rollback.call
120
+ rescue Object => e
121
+ logger.info "exception while rolling back: #{e.class}, #{e.message}", frame.task.fully_qualified_name
122
+ ensure
123
+ pop_task_call_frame
124
+ end
125
+ end
126
+ end
127
+
128
+ def push_task_call_frame(task)
129
+ frame = TaskCallFrame.new(task)
130
+ task_call_frames.push frame
131
+ end
132
+
133
+ def pop_task_call_frame
134
+ task_call_frames.pop
135
+ end
136
+
137
+ # Invokes the task's body directly, without setting up the call frame.
138
+ def invoke_task_directly(task)
139
+ task.namespace.instance_eval(&task.body)
140
+ end
141
+ end
142
+ end
143
+ end
@@ -0,0 +1,197 @@
1
+ module Capistrano
2
+ class Configuration
3
+ module Loading
4
+ def self.included(base) #:nodoc:
5
+ base.send :alias_method, :initialize_without_loading, :initialize
6
+ base.send :alias_method, :initialize, :initialize_with_loading
7
+ base.extend ClassMethods
8
+ end
9
+
10
+ module ClassMethods
11
+ # Used by third-party task bundles to identify the capistrano
12
+ # configuration that is loading them. Its return value is not reliable
13
+ # in other contexts. If +require_config+ is not false, an exception
14
+ # will be raised if the current configuration is not set.
15
+ def instance(require_config=false)
16
+ config = Thread.current[:capistrano_configuration]
17
+ if require_config && config.nil?
18
+ raise LoadError, "Please require this file from within a Capistrano recipe"
19
+ end
20
+ config
21
+ end
22
+
23
+ # Used internally by Capistrano to specify the current configuration
24
+ # before loading a third-party task bundle.
25
+ def instance=(config)
26
+ Thread.current[:capistrano_configuration] = config
27
+ end
28
+
29
+ # Used internally by Capistrano to track which recipes have been loaded
30
+ # via require, so that they may be successfully reloaded when require
31
+ # is called again.
32
+ def recipes_per_feature
33
+ @recipes_per_feature ||= {}
34
+ end
35
+
36
+ # Used internally to determine what the current "feature" being
37
+ # required is. This is used to track which files load which recipes
38
+ # via require.
39
+ def current_feature
40
+ Thread.current[:capistrano_current_feature]
41
+ end
42
+
43
+ # Used internally to specify the current file being required, so that
44
+ # any recipes loaded by that file can be remembered. This allows
45
+ # recipes loaded via require to be correctly reloaded in different
46
+ # Configuration instances in the same Ruby instance.
47
+ def current_feature=(feature)
48
+ Thread.current[:capistrano_current_feature] = feature
49
+ end
50
+ end
51
+
52
+ # The load paths used for locating recipe files.
53
+ attr_reader :load_paths
54
+
55
+ def initialize_with_loading(*args) #:nodoc:
56
+ initialize_without_loading(*args)
57
+ @load_paths = [".", File.expand_path(File.join(File.dirname(__FILE__), "../recipes"))]
58
+ @loaded_features = []
59
+ end
60
+ private :initialize_with_loading
61
+
62
+ # Load a configuration file or string into this configuration.
63
+ #
64
+ # Usage:
65
+ #
66
+ # load("recipe"):
67
+ # Look for and load the contents of 'recipe.rb' into this
68
+ # configuration.
69
+ #
70
+ # load(:file => "recipe"):
71
+ # same as above
72
+ #
73
+ # load(:string => "set :scm, :subversion"):
74
+ # Load the given string as a configuration specification.
75
+ #
76
+ # load { ... }
77
+ # Load the block in the context of the configuration.
78
+ def load(*args, &block)
79
+ options = args.last.is_a?(Hash) ? args.pop : {}
80
+
81
+ if block
82
+ raise ArgumentError, "loading a block requires 0 arguments" unless options.empty? && args.empty?
83
+ load(:proc => block)
84
+
85
+ elsif args.any?
86
+ args.each { |arg| load options.merge(:file => arg) }
87
+
88
+ elsif options[:file]
89
+ load_from_file(options[:file], options[:name])
90
+
91
+ elsif options[:string]
92
+ remember_load(options) unless options[:reloading]
93
+ instance_eval(options[:string], options[:name] || "<eval>")
94
+
95
+ elsif options[:proc]
96
+ remember_load(options) unless options[:reloading]
97
+ instance_eval(&options[:proc])
98
+
99
+ else
100
+ raise ArgumentError, "don't know how to load #{options.inspect}"
101
+ end
102
+ end
103
+
104
+ # Require another file. This is identical to the standard require method,
105
+ # with the exception that it sets the receiver as the "current" configuration
106
+ # so that third-party task bundles can include themselves relative to
107
+ # that configuration.
108
+ #
109
+ # This is a bit more complicated than an initial review would seem to
110
+ # necessitate, but the use case that complicates things is this: An
111
+ # advanced user wants to embed capistrano, and needs to instantiate
112
+ # more than one capistrano configuration at a time. They also want each
113
+ # configuration to require a third-party capistrano extension. Using a
114
+ # naive require implementation, this would allow the first configuration
115
+ # to successfully load the third-party extension, but the require would
116
+ # fail for the second configuration because the extension has already
117
+ # been loaded.
118
+ #
119
+ # To work around this, we do a few things:
120
+ #
121
+ # 1. Each time a 'require' is invoked inside of a capistrano recipe,
122
+ # we remember the arguments (see "current_feature").
123
+ # 2. Each time a 'load' is invoked inside of a capistrano recipe, and
124
+ # "current_feature" is not nil (meaning we are inside of a pending
125
+ # require) we remember the options (see "remember_load" and
126
+ # "recipes_per_feature").
127
+ # 3. Each time a 'require' is invoked inside of a capistrano recipe,
128
+ # we check to see if this particular configuration has ever seen these
129
+ # arguments to require (see @loaded_features), and if not, we proceed
130
+ # as if the file had never been required. If the superclass' require
131
+ # returns false (meaning, potentially, that the file has already been
132
+ # required), then we look in the recipes_per_feature collection and
133
+ # load any remembered recipes from there.
134
+ #
135
+ # It's kind of a bear, but it works, and works transparently. Note that
136
+ # a simpler implementation would just muck with $", allowing files to be
137
+ # required multiple times, but that will cause warnings (and possibly
138
+ # errors) if the file to be required contains constant definitions and
139
+ # such, alongside (or instead of) capistrano recipe definitions.
140
+ def require(*args) #:nodoc:
141
+ # look to see if this specific configuration instance has ever seen
142
+ # these arguments to require before
143
+ if @loaded_features.include?(args)
144
+ return false
145
+ end
146
+
147
+ @loaded_features << args
148
+ begin
149
+ original_instance, self.class.instance = self.class.instance, self
150
+ original_feature, self.class.current_feature = self.class.current_feature, args
151
+
152
+ result = super
153
+ if !result # file has been required previously, load up the remembered recipes
154
+ list = self.class.recipes_per_feature[args] || []
155
+ list.each { |options| load(options.merge(:reloading => true)) }
156
+ end
157
+
158
+ return result
159
+ ensure
160
+ # restore the original, so that require's can be nested
161
+ self.class.instance = original_instance
162
+ self.class.current_feature = original_feature
163
+ end
164
+ end
165
+
166
+ private
167
+
168
+ # Load a recipe from the named file. If +name+ is given, the file will
169
+ # be reported using that name.
170
+ def load_from_file(file, name=nil)
171
+ file = find_file_in_load_path(file) unless File.file?(file)
172
+ load :string => File.read(file), :name => name || file
173
+ end
174
+
175
+ def find_file_in_load_path(file)
176
+ load_paths.each do |path|
177
+ ["", ".rb"].each do |ext|
178
+ name = File.join(path, "#{file}#{ext}")
179
+ return name if File.file?(name)
180
+ end
181
+ end
182
+
183
+ raise LoadError, "no such file to load -- #{file}"
184
+ end
185
+
186
+ # If a file is being required, the options associated with loading a
187
+ # recipe are remembered in the recipes_per_feature archive under the
188
+ # name of the file currently being required.
189
+ def remember_load(options)
190
+ if self.class.current_feature
191
+ list = (self.class.recipes_per_feature[self.class.current_feature] ||= [])
192
+ list << options
193
+ end
194
+ end
195
+ end
196
+ end
197
+ end
@@ -0,0 +1,197 @@
1
+ require 'capistrano/task_definition'
2
+
3
+ module Capistrano
4
+ class Configuration
5
+ module Namespaces
6
+ DEFAULT_TASK = :default
7
+
8
+ def self.included(base) #:nodoc:
9
+ base.send :alias_method, :initialize_without_namespaces, :initialize
10
+ base.send :alias_method, :initialize, :initialize_with_namespaces
11
+ end
12
+
13
+ # The name of this namespace. Defaults to +nil+ for the top-level
14
+ # namespace.
15
+ attr_reader :name
16
+
17
+ # The parent namespace of this namespace. Returns +nil+ for the top-level
18
+ # namespace.
19
+ attr_reader :parent
20
+
21
+ # The hash of tasks defined for this namespace.
22
+ attr_reader :tasks
23
+
24
+ # The hash of namespaces defined for this namespace.
25
+ attr_reader :namespaces
26
+
27
+ def initialize_with_namespaces(*args) #:nodoc:
28
+ @name = @parent = nil
29
+ initialize_without_namespaces(*args)
30
+ @tasks = {}
31
+ @namespaces = {}
32
+ end
33
+ private :initialize_with_namespaces
34
+
35
+ # Returns the top-level namespace (the one with no parent).
36
+ def top
37
+ return parent.top if parent
38
+ return self
39
+ end
40
+
41
+ # Returns the fully-qualified name of this namespace, or nil if the
42
+ # namespace is at the top-level.
43
+ def fully_qualified_name
44
+ return nil if name.nil?
45
+ [parent.fully_qualified_name, name].compact.join(":")
46
+ end
47
+
48
+ # Describe the next task to be defined. The given text will be attached to
49
+ # the next task that is defined and used as its description.
50
+ def desc(text)
51
+ @next_description = text
52
+ end
53
+
54
+ # Returns the value set by the last, pending "desc" call. If +reset+ is
55
+ # not false, the value will be reset immediately afterwards.
56
+ def next_description(reset=false)
57
+ @next_description
58
+ ensure
59
+ @next_description = nil if reset
60
+ end
61
+
62
+ # Open a namespace in which to define new tasks. If the namespace was
63
+ # defined previously, it will be reopened, otherwise a new namespace
64
+ # will be created for the given name.
65
+ def namespace(name, &block)
66
+ name = name.to_sym
67
+ raise ArgumentError, "expected a block" unless block_given?
68
+
69
+ namespace_already_defined = namespaces.key?(name)
70
+ if all_methods.any? { |m| m.to_sym == name } && !namespace_already_defined
71
+ thing = tasks.key?(name) ? "task" : "method"
72
+ raise ArgumentError, "defining a namespace named `#{name}' would shadow an existing #{thing} with that name"
73
+ end
74
+
75
+ namespaces[name] ||= Namespace.new(name, self)
76
+ namespaces[name].instance_eval(&block)
77
+
78
+ # make sure any open description gets terminated
79
+ namespaces[name].desc(nil)
80
+
81
+ if !namespace_already_defined
82
+ metaclass = class << self; self; end
83
+ metaclass.send(:define_method, name) { namespaces[name] }
84
+ end
85
+ end
86
+
87
+ # Describe a new task. If a description is active (see #desc), it is added
88
+ # to the options under the <tt>:desc</tt> key. The new task is added to
89
+ # the namespace.
90
+ def task(name, options={}, &block)
91
+ name = name.to_sym
92
+ raise ArgumentError, "expected a block" unless block_given?
93
+
94
+ task_already_defined = tasks.key?(name)
95
+ if all_methods.any? { |m| m.to_sym == name } && !task_already_defined
96
+ thing = namespaces.key?(name) ? "namespace" : "method"
97
+ raise ArgumentError, "defining a task named `#{name}' would shadow an existing #{thing} with that name"
98
+ end
99
+
100
+ tasks[name] = TaskDefinition.new(name, self, {:desc => next_description(:reset)}.merge(options), &block)
101
+
102
+ if !task_already_defined
103
+ metaclass = class << self; self; end
104
+ metaclass.send(:define_method, name) { execute_task(tasks[name]) }
105
+ end
106
+ end
107
+
108
+ # Find the task with the given name, where name is the fully-qualified
109
+ # name of the task. This will search into the namespaces and return
110
+ # the referenced task, or nil if no such task can be found. If the name
111
+ # refers to a namespace, the task in that namespace named "default"
112
+ # will be returned instead, if one exists.
113
+ def find_task(name)
114
+ parts = name.to_s.split(/:/)
115
+ tail = parts.pop.to_sym
116
+
117
+ ns = self
118
+ until parts.empty?
119
+ next_part = parts.shift
120
+ ns = next_part.empty? ? nil : ns.namespaces[next_part.to_sym]
121
+ return nil if ns.nil?
122
+ end
123
+
124
+ if ns.namespaces.key?(tail)
125
+ ns = ns.namespaces[tail]
126
+ tail = DEFAULT_TASK
127
+ end
128
+
129
+ ns.tasks[tail]
130
+ end
131
+
132
+ # Given a task name, this will search the current namespace, and all
133
+ # parent namespaces, looking for a task that matches the name, exactly.
134
+ # It returns the task, if found, or nil, if not.
135
+ def search_task(name)
136
+ name = name.to_sym
137
+ ns = self
138
+
139
+ until ns.nil?
140
+ return ns.tasks[name] if ns.tasks.key?(name)
141
+ ns = ns.parent
142
+ end
143
+
144
+ return nil
145
+ end
146
+
147
+ # Returns the default task for this namespace. This will be +nil+ if
148
+ # the namespace is at the top-level, and will otherwise return the
149
+ # task named "default". If no such task exists, +nil+ will be returned.
150
+ def default_task
151
+ return nil if parent.nil?
152
+ return tasks[DEFAULT_TASK]
153
+ end
154
+
155
+ # Returns the tasks in this namespace as an array of TaskDefinition
156
+ # objects. If a non-false parameter is given, all tasks in all
157
+ # namespaces under this namespace will be returned as well.
158
+ def task_list(all=false)
159
+ list = tasks.values
160
+ namespaces.each { |name,space| list.concat(space.task_list(:all)) } if all
161
+ list
162
+ end
163
+
164
+ private
165
+
166
+ def all_methods
167
+ public_methods.concat(protected_methods).concat(private_methods)
168
+ end
169
+
170
+ class Namespace
171
+ def initialize(name, parent)
172
+ @parent = parent
173
+ @name = name
174
+ end
175
+
176
+ def role(*args)
177
+ raise NotImplementedError, "roles cannot be defined in a namespace"
178
+ end
179
+
180
+ def respond_to?(sym, include_priv=false)
181
+ super || parent.respond_to?(sym, include_priv)
182
+ end
183
+
184
+ def method_missing(sym, *args, &block)
185
+ if parent.respond_to?(sym)
186
+ parent.send(sym, *args, &block)
187
+ else
188
+ super
189
+ end
190
+ end
191
+
192
+ include Capistrano::Configuration::Namespaces
193
+ undef :desc, :next_description
194
+ end
195
+ end
196
+ end
197
+ end