Skip to content

Commit

Permalink
Added before(), after(), and on() callback creation methods
Browse files Browse the repository at this point in the history
git-svn-id: http://svn.rubyonrails.org/rails/tools/capistrano@6695 5ecf4fe2-1ee6-0310-87b1-e25e094e27de
  • Loading branch information
jamis committed May 8, 2007
1 parent 27211a9 commit 939dd24
Show file tree
Hide file tree
Showing 8 changed files with 387 additions and 39 deletions.
2 changes: 2 additions & 0 deletions CHANGELOG
@@ -1,5 +1,7 @@
*SVN*

* Added before(), after(), and on() callback creation methods. [Jamis Buck]

* Fix broken check! method for some deployment strategies. [Jamis Buck]

* deploy:cold should also run migrations before starting the app [Jamis Buck]
Expand Down
41 changes: 41 additions & 0 deletions lib/capistrano/callback.rb
@@ -0,0 +1,41 @@
module Capistrano
class Callback
attr_reader :source, :options, :only, :except

def initialize(source, options={})
@source = source
@options = options
@only = Array(options[:only]).map { |v| v.to_s }
@except = Array(options[:except]).map { |v| v.to_s }
end

def applies_to?(task)
if only.any?
return only.include?(task.fully_qualified_name)
elsif except.any?
return !except.include?(task.fully_qualified_name)
else
return true
end
end
end

class ProcCallback < Callback
def call
source.call
end
end

class TaskCallback < Callback
attr_reader :config

def initialize(config, source, options={})
super(source, options)
@config = config
end

def call
config.find_and_execute_task(source)
end
end
end
4 changes: 4 additions & 0 deletions lib/capistrano/configuration.rb
@@ -1,5 +1,6 @@
require 'capistrano/logger'

require 'capistrano/configuration/callbacks'
require 'capistrano/configuration/connections'
require 'capistrano/configuration/execution'
require 'capistrano/configuration/loading'
Expand Down Expand Up @@ -30,5 +31,8 @@ def initialize #:nodoc:

# Mix in the actions
include Actions::FileTransfer, Actions::Inspect, Actions::Invocation

# Must mix last, because it hooks into previously defined methods
include Callbacks
end
end
142 changes: 142 additions & 0 deletions lib/capistrano/configuration/callbacks.rb
@@ -0,0 +1,142 @@
require 'capistrano/callback'

module Capistrano
class Configuration
module Callbacks
def self.included(base) #:nodoc:
%w(initialize execute_task).each do |method|
base.send :alias_method, "#{method}_without_callbacks", method
base.send :alias_method, method, "#{method}_with_callbacks"
end
end

# The hash of callbacks that have been registered for this configuration
attr_reader :callbacks

def initialize_with_callbacks(*args) #:nodoc:
initialize_without_callbacks(*args)
@callbacks = {}
end

def execute_task_with_callbacks(task) #:nodoc:
before = find_hook(task, :before)
execute_task(before) if before

trigger :before, task

result = execute_task_without_callbacks(task)

trigger :after, task

after = find_hook(task, :after)
execute_task(after) if after

return result
end

# Defines a callback to be invoked before the given task. You must
# specify the fully-qualified task name, both for the primary task, and
# for the task(s) to be executed before. Alternatively, you can pass a
# block to be executed before the given task.
#
# before "deploy:update_code", :record_difference
# before :deploy, "custom:log_deploy"
# before :deploy, :this, "then:this", "and:then:this"
# before :some_task do
# puts "an anonymous hook!"
# end
#
# This just provides a convenient interface to the more general #on method.
def before(task_name, *args, &block)
options = args.last.is_a?(Hash) ? args.pop : {}
args << options.merge(:only => task_name)
on :before, *args, &block
end

# Defines a callback to be invoked after the given task. You must
# specify the fully-qualified task name, both for the primary task, and
# for the task(s) to be executed after. Alternatively, you can pass a
# block to be executed after the given task.
#
# after "deploy:update_code", :log_difference
# after :deploy, "custom:announce"
# after :deploy, :this, "then:this", "and:then:this"
# after :some_task do
# puts "an anonymous hook!"
# end
#
# This just provides a convenient interface to the more general #on method.
def after(task_name, *args, &block)
options = args.last.is_a?(Hash) ? args.pop : {}
args << options.merge(:only => task_name)
on :after, *args, &block
end

# Defines one or more callbacks to be invoked in response to some event.
# Capistrano currently understands the following events:
#
# * :before, triggered before a task is invoked
# * :after, triggered after a task is invoked
#
# Specify the (fully-qualified) task names that you want invoked in
# response to the event. Alternatively, you can specify a block to invoke
# when the event is triggered. You can also pass a hash of options as the
# last parameter, which may include either of two keys:
#
# * :only, should specify an array of task names. Restricts this callback
# so that it will only fire when the event applies to those tasks.
# * :except, should specify an array of task names. Restricts this callback
# so that it will never fire when the event applies to those tasks.
#
# Usage:
#
# on :before, "some:hook", "another:hook", :only => "deploy:update"
# on :after, "some:hook", :except => "deploy:symlink"
# on :before, "global:hook"
# on :after, :only => :deploy do
# puts "after deploy here"
# end
def on(event, *args, &block)
options = args.last.is_a?(Hash) ? args.pop : {}
callbacks[event] ||= []

if args.empty? && block.nil?
raise ArgumentError, "please specify either a task name or a block to invoke"
elsif args.any? && block
raise ArgumentError, "please specify only a task name or a block, but not both"
elsif block
callbacks[event] << ProcCallback.new(block, options)
else
args.each do |name|
callbacks[event] << TaskCallback.new(self, name, options)
end
end
end

# Trigger the named event for the named task. All associated callbacks
# will be fired, in the order they were defined.
def trigger(event, task)
pending = Array(callbacks[event]).select { |c| c.applies_to?(task) }
if pending.any?
logger.trace "triggering #{event} callbacks for `#{task.fully_qualified_name}'"
pending.each { |callback| callback.call }
end
end

private

# Looks for before_foo or after_foo tasks. This method of extending tasks
# is now discouraged (though not formally deprecated). You should use the
# before and after methods to declare hooks for such callbacks.
def find_hook(task, hook)
if task == task.namespace.default_task
result = task.namespace.search_task("#{hook}_#{task.namespace.name}")
return result if result
end

task.namespace.search_task("#{hook}_#{task.name}")
end

end
end
end
26 changes: 4 additions & 22 deletions lib/capistrano/configuration/execution.rb
Expand Up @@ -75,20 +75,11 @@ def current_task
# Executes the task with the given name, including the before and after
# hooks.
def execute_task(task)
before = find_hook(task, :before)
execute_task(before) if before
logger.debug "executing `#{task.fully_qualified_name}'"

begin
push_task_call_frame(task)
result = task.namespace.instance_eval(&task.body)
ensure
pop_task_call_frame
end

after = find_hook(task, :after)
execute_task(after) if after
result
push_task_call_frame(task)
task.namespace.instance_eval(&task.body)
ensure
pop_task_call_frame
end

# Attempts to locate the task at the given fully-qualified path, and
Expand All @@ -101,15 +92,6 @@ def find_and_execute_task(path)

protected

def find_hook(task, hook)
if task == task.namespace.default_task
result = task.namespace.search_task("#{hook}_#{task.namespace.name}")
return result if result
end

task.namespace.search_task("#{hook}_#{task.name}")
end

def rollback!
# throw the task back on the stack so that roles are properly
# interpreted in the scope of the task in question.
Expand Down
1 change: 1 addition & 0 deletions lib/capistrano/shell.rb
Expand Up @@ -128,6 +128,7 @@ def help
# establish connections to them if necessary. Return the list of
# servers (names).
def connect(task)
# FIXME this is broken!
servers = task.servers(:refresh)
needing_connections = servers.reject { |s| configuration.sessions.key?(s.host) }
unless needing_connections.empty?
Expand Down

0 comments on commit 939dd24

Please sign in to comment.