Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Fetching contributors…

Cannot retrieve contributors at this time

224 lines (179 sloc) 7.216 kb
require 'capistrano/task_definition'
module Capistrano
class Configuration
module Namespaces
DEFAULT_TASK = :default
def self.included(base) #:nodoc:
base.send :alias_method, :initialize_without_namespaces, :initialize
base.send :alias_method, :initialize, :initialize_with_namespaces
end
# The name of this namespace. Defaults to +nil+ for the top-level
# namespace.
attr_reader :name
# The parent namespace of this namespace. Returns +nil+ for the top-level
# namespace.
attr_reader :parent
# The hash of tasks defined for this namespace.
attr_reader :tasks
# The hash of namespaces defined for this namespace.
attr_reader :namespaces
def initialize_with_namespaces(*args) #:nodoc:
@name = @parent = nil
initialize_without_namespaces(*args)
@tasks = {}
@namespaces = {}
end
private :initialize_with_namespaces
# Returns the top-level namespace (the one with no parent).
def top
return parent.top if parent
return self
end
# Returns the fully-qualified name of this namespace, or nil if the
# namespace is at the top-level.
def fully_qualified_name
return nil if name.nil?
[parent.fully_qualified_name, name].compact.join(":")
end
# Describe the next task to be defined. The given text will be attached to
# the next task that is defined and used as its description.
def desc(text)
@next_description = text
end
# Returns the value set by the last, pending "desc" call. If +reset+ is
# not false, the value will be reset immediately afterwards.
def next_description(reset=false)
@next_description
ensure
@next_description = nil if reset
end
# Open a namespace in which to define new tasks. If the namespace was
# defined previously, it will be reopened, otherwise a new namespace
# will be created for the given name.
def namespace(name, &block)
name = name.to_sym
raise ArgumentError, "expected a block" unless block_given?
namespace_already_defined = namespaces.key?(name)
if all_methods.any? { |m| m.to_sym == name } && !namespace_already_defined
thing = tasks.key?(name) ? "task" : "method"
raise ArgumentError, "defining a namespace named `#{name}' would shadow an existing #{thing} with that name"
end
namespaces[name] ||= Namespace.new(name, self)
namespaces[name].instance_eval(&block)
# make sure any open description gets terminated
namespaces[name].desc(nil)
if !namespace_already_defined
metaclass = class << self; self; end
metaclass.send(:define_method, name) { namespaces[name] }
end
end
# Describe a new task. If a description is active (see #desc), it is added
# to the options under the <tt>:desc</tt> key. The new task is added to
# the namespace.
def task(name, options={}, &block)
name = name.to_sym
raise ArgumentError, "expected a block" unless block_given?
task_already_defined = tasks.key?(name)
if all_methods.any? { |m| m.to_sym == name } && !task_already_defined
thing = namespaces.key?(name) ? "namespace" : "method"
raise ArgumentError, "defining a task named `#{name}' would shadow an existing #{thing} with that name"
end
task = TaskDefinition.new(name, self, {:desc => next_description(:reset)}.merge(options), &block)
define_task(task)
end
def define_task(task)
tasks[task.name] = task
metaclass = class << self; self; end
metaclass.send(:define_method, task.name) { execute_task(tasks[task.name]) }
end
# Find the task with the given name, where name is the fully-qualified
# name of the task. This will search into the namespaces and return
# the referenced task, or nil if no such task can be found. If the name
# refers to a namespace, the task in that namespace named "default"
# will be returned instead, if one exists.
def find_task(name)
parts = name.to_s.split(/:/)
tail = parts.pop.to_sym
ns = self
until parts.empty?
next_part = parts.shift
ns = next_part.empty? ? nil : ns.namespaces[next_part.to_sym]
return nil if ns.nil?
end
if ns.namespaces.key?(tail)
ns = ns.namespaces[tail]
tail = DEFAULT_TASK
end
ns.tasks[tail]
end
# Given a task name, this will search the current namespace, and all
# parent namespaces, looking for a task that matches the name, exactly.
# It returns the task, if found, or nil, if not.
def search_task(name)
name = name.to_sym
ns = self
until ns.nil?
return ns.tasks[name] if ns.tasks.key?(name)
ns = ns.parent
end
return nil
end
# Returns the default task for this namespace. This will be +nil+ if
# the namespace is at the top-level, and will otherwise return the
# task named "default". If no such task exists, +nil+ will be returned.
def default_task
return nil if parent.nil?
return tasks[DEFAULT_TASK]
end
# Returns the tasks in this namespace as an array of TaskDefinition
# objects. If a non-false parameter is given, all tasks in all
# namespaces under this namespace will be returned as well.
def task_list(all=false)
list = tasks.values
namespaces.each { |name,space| list.concat(space.task_list(:all)) } if all
list
end
private
def all_methods
public_methods.concat(protected_methods).concat(private_methods)
end
class Namespace
def initialize(name, parent)
@parent = parent
@name = name
end
def role(*args)
raise NotImplementedError, "roles cannot be defined in a namespace"
end
def respond_to?(sym, include_priv=false)
super || parent.respond_to?(sym, include_priv)
end
def method_missing(sym, *args, &block)
if parent.respond_to?(sym)
parent.send(sym, *args, &block)
else
super
end
end
include Capistrano::Configuration::AliasTask
include Capistrano::Configuration::Namespaces
undef :desc, :next_description
end
end
end
end
module Kernel
class << self
alias_method :method_added_without_capistrano, :method_added
# Detect method additions to Kernel and remove them in the Namespace class
def method_added(name)
result = method_added_without_capistrano(name)
return result if self != Kernel
namespace = Capistrano::Configuration::Namespaces::Namespace
if namespace.method_defined?(name) && namespace.instance_method(name).owner == Kernel
namespace.send :undef_method, name
end
result
end
end
end
Jump to Line
Something went wrong with that request. Please try again.