Module: Capistrano::Configuration::Loading

Included in:
Capistrano::Configuration
Defined in:
lib/capistrano/configuration/loading.rb

Defined Under Namespace

Modules: ClassMethods

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Instance Attribute Details

#load_pathsObject (readonly)

The load paths used for locating recipe files.


53
54
55
# File 'lib/capistrano/configuration/loading.rb', line 53

def load_paths
  @load_paths
end

Class Method Details

.included(base) ⇒ Object

:nodoc:


4
5
6
7
8
# File 'lib/capistrano/configuration/loading.rb', line 4

def self.included(base) #:nodoc:
  base.send :alias_method, :initialize_without_loading, :initialize
  base.send :alias_method, :initialize, :initialize_with_loading
  base.extend ClassMethods
end

Instance Method Details

#load(*args, &block) ⇒ Object

Load a configuration file or string into this configuration.

Usage:

load("recipe"):
  Look for and load the contents of 'recipe.rb' into this
  configuration.

load(:file => "recipe"):
  same as above

load(:string => "set :scm, :subversion"):
  Load the given string as a configuration specification.

load { ... }
  Load the block in the context of the configuration.

78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
# File 'lib/capistrano/configuration/loading.rb', line 78

def load(*args, &block)
  options = args.last.is_a?(Hash) ? args.pop : {}

  if block
    raise ArgumentError, "loading a block requires 0 arguments" unless options.empty? && args.empty?
    load(:proc => block)

  elsif args.any?
    args.each { |arg| load options.merge(:file => arg) }

  elsif options[:file]
    load_from_file(options[:file], options[:name])

  elsif options[:string]
    remember_load(options) unless options[:reloading]
    instance_eval(options[:string], options[:name] || "<eval>")

  elsif options[:proc]
    remember_load(options) unless options[:reloading]
    instance_eval(&options[:proc])

  else
    raise ArgumentError, "don't know how to load #{options.inspect}"
  end
end

#require(*args) ⇒ Object

Require another file. This is identical to the standard require method, with the exception that it sets the receiver as the “current” configuration so that third-party task bundles can include themselves relative to that configuration.

This is a bit more complicated than an initial review would seem to necessitate, but the use case that complicates things is this: An advanced user wants to embed capistrano, and needs to instantiate more than one capistrano configuration at a time. They also want each configuration to require a third-party capistrano extension. Using a naive require implementation, this would allow the first configuration to successfully load the third-party extension, but the require would fail for the second configuration because the extension has already been loaded.

To work around this, we do a few things:

  1. Each time a 'require' is invoked inside of a capistrano recipe, we remember the arguments (see “current_feature”).

  2. Each time a 'load' is invoked inside of a capistrano recipe, and “current_feature” is not nil (meaning we are inside of a pending require) we remember the options (see “remember_load” and “recipes_per_feature”).

  3. Each time a 'require' is invoked inside of a capistrano recipe, we check to see if this particular configuration has ever seen these arguments to require (see @loaded_features), and if not, we proceed as if the file had never been required. If the superclass' require returns false (meaning, potentially, that the file has already been required), then we look in the recipes_per_feature collection and load any remembered recipes from there.

It's kind of a bear, but it works, and works transparently. Note that a simpler implementation would just muck with $“, allowing files to be required multiple times, but that will cause warnings (and possibly errors) if the file to be required contains constant definitions and such, alongside (or instead of) capistrano recipe definitions.


140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
# File 'lib/capistrano/configuration/loading.rb', line 140

def require(*args) #:nodoc:
  # look to see if this specific configuration instance has ever seen
  # these arguments to require before
  if @loaded_features.include?(args)
    return false 
  end
  
  @loaded_features << args
  begin
    original_instance, self.class.instance = self.class.instance, self
    original_feature, self.class.current_feature = self.class.current_feature, args

    result = super
    if !result # file has been required previously, load up the remembered recipes
      list = self.class.recipes_per_feature[args] || []
      list.each { |options| load(options.merge(:reloading => true)) }
    end

    return result
  ensure
    # restore the original, so that require's can be nested
    self.class.instance = original_instance
    self.class.current_feature = original_feature
  end
end