cape 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- data/.gemtest +0 -0
- data/.gitignore +6 -0
- data/.travis.yml +7 -0
- data/.yardopts +1 -0
- data/Gemfile +18 -0
- data/History.markdown +7 -0
- data/MIT-LICENSE.markdown +10 -0
- data/README.markdown +157 -0
- data/Rakefile +49 -0
- data/cape.gemspec +36 -0
- data/features/dsl/each_rake_task/with_defined_namespace_argument.feature +43 -0
- data/features/dsl/each_rake_task/with_defined_task_argument.feature +37 -0
- data/features/dsl/each_rake_task/with_undefined_argument.feature +31 -0
- data/features/dsl/each_rake_task/without_arguments.feature +81 -0
- data/features/dsl/mirror_rake_tasks/inside_capistrano_namespace/with_defined_namespace_argument.feature +93 -0
- data/features/dsl/mirror_rake_tasks/inside_capistrano_namespace/with_defined_task_argument.feature +66 -0
- data/features/dsl/mirror_rake_tasks/inside_capistrano_namespace/with_undefined_argument.feature +39 -0
- data/features/dsl/mirror_rake_tasks/inside_capistrano_namespace/without_arguments.feature +263 -0
- data/features/dsl/mirror_rake_tasks/with_defined_namespace_argument.feature +85 -0
- data/features/dsl/mirror_rake_tasks/with_defined_task_argument.feature +60 -0
- data/features/dsl/mirror_rake_tasks/with_undefined_argument.feature +35 -0
- data/features/dsl/mirror_rake_tasks/without_arguments.feature +243 -0
- data/features/step_definitions.rb +33 -0
- data/features/support/env.rb +1 -0
- data/features/task_invocation/nonparameterized.feature +69 -0
- data/features/task_invocation/parameterized.feature +70 -0
- data/lib/cape.rb +22 -0
- data/lib/cape/capistrano.rb +86 -0
- data/lib/cape/core_ext.rb +10 -0
- data/lib/cape/core_ext/hash.rb +24 -0
- data/lib/cape/core_ext/symbol.rb +25 -0
- data/lib/cape/dsl.rb +81 -0
- data/lib/cape/rake.rb +60 -0
- data/lib/cape/strings.rb +25 -0
- data/lib/cape/version.rb +6 -0
- data/spec/cape/capistrano_spec.rb +0 -0
- data/spec/cape/core_ext/hash_spec.rb +12 -0
- data/spec/cape/core_ext/symbol_spec.rb +7 -0
- data/spec/cape/dsl_spec.rb +128 -0
- data/spec/cape/rake_spec.rb +0 -0
- data/spec/cape/strings_spec.rb +44 -0
- data/spec/cape/task_spec.rb +0 -0
- data/spec/cape/version_spec.rb +6 -0
- data/spec/cape_spec.rb +5 -0
- metadata +192 -0
@@ -0,0 +1 @@
|
|
1
|
+
require 'aruba/cucumber'
|
@@ -0,0 +1,69 @@
|
|
1
|
+
Feature: Invoking parameterless Rake tasks via Capistrano
|
2
|
+
|
3
|
+
In order to invoke Rake tasks via Capistrano,
|
4
|
+
As a developer using Cape,
|
5
|
+
I want to use the `cap` command.
|
6
|
+
|
7
|
+
Scenario: invoke Rake task 'with_period'
|
8
|
+
Given a full-featured Rakefile
|
9
|
+
And a file named "Capfile" with:
|
10
|
+
"""
|
11
|
+
load 'deploy' if respond_to?(:namespace) # cap2 differentiator
|
12
|
+
|
13
|
+
# Uncomment if you are using Rails' asset pipeline
|
14
|
+
# load 'deploy/assets'
|
15
|
+
|
16
|
+
Dir['vendor/gems/*/recipes/*.rb','vendor/plugins/*/recipes/*.rb'].each { |plugin| load(plugin) }
|
17
|
+
|
18
|
+
load 'config/deploy' # remove this line to skip loading any of the default tasks
|
19
|
+
require 'cape'
|
20
|
+
|
21
|
+
Cape do
|
22
|
+
mirror_rake_tasks
|
23
|
+
end
|
24
|
+
"""
|
25
|
+
And a file named "config/deploy.rb" with:
|
26
|
+
"""
|
27
|
+
require 'cape'
|
28
|
+
|
29
|
+
Cape do
|
30
|
+
mirror_rake_tasks
|
31
|
+
end
|
32
|
+
"""
|
33
|
+
When I run `cap with_period`
|
34
|
+
Then the output should contain:
|
35
|
+
"""
|
36
|
+
* executing `with_period'
|
37
|
+
"""
|
38
|
+
|
39
|
+
Scenario: invoke Rake task 'my_namespace:in_a_namespace'
|
40
|
+
Given a full-featured Rakefile
|
41
|
+
And a file named "Capfile" with:
|
42
|
+
"""
|
43
|
+
load 'deploy' if respond_to?(:namespace) # cap2 differentiator
|
44
|
+
|
45
|
+
# Uncomment if you are using Rails' asset pipeline
|
46
|
+
# load 'deploy/assets'
|
47
|
+
|
48
|
+
Dir['vendor/gems/*/recipes/*.rb','vendor/plugins/*/recipes/*.rb'].each { |plugin| load(plugin) }
|
49
|
+
|
50
|
+
load 'config/deploy' # remove this line to skip loading any of the default tasks
|
51
|
+
require 'cape'
|
52
|
+
|
53
|
+
Cape do
|
54
|
+
mirror_rake_tasks
|
55
|
+
end
|
56
|
+
"""
|
57
|
+
And a file named "config/deploy.rb" with:
|
58
|
+
"""
|
59
|
+
require 'cape'
|
60
|
+
|
61
|
+
Cape do
|
62
|
+
mirror_rake_tasks
|
63
|
+
end
|
64
|
+
"""
|
65
|
+
When I run `cap my_namespace:in_a_namespace`
|
66
|
+
Then the output should contain:
|
67
|
+
"""
|
68
|
+
* executing `my_namespace:in_a_namespace'
|
69
|
+
"""
|
@@ -0,0 +1,70 @@
|
|
1
|
+
Feature: Invoking parameterized Rake tasks via Capistrano
|
2
|
+
|
3
|
+
In order to invoke Rake tasks via Capistrano,
|
4
|
+
As a developer using Cape,
|
5
|
+
I want to use the `cap` command.
|
6
|
+
|
7
|
+
Scenario: invoke Rake task 'with_one_arg' without an argument
|
8
|
+
Given a full-featured Rakefile
|
9
|
+
And a file named "Capfile" with:
|
10
|
+
"""
|
11
|
+
load 'deploy' if respond_to?(:namespace) # cap2 differentiator
|
12
|
+
|
13
|
+
# Uncomment if you are using Rails' asset pipeline
|
14
|
+
# load 'deploy/assets'
|
15
|
+
|
16
|
+
Dir['vendor/gems/*/recipes/*.rb','vendor/plugins/*/recipes/*.rb'].each { |plugin| load(plugin) }
|
17
|
+
|
18
|
+
load 'config/deploy' # remove this line to skip loading any of the default tasks
|
19
|
+
require 'cape'
|
20
|
+
|
21
|
+
Cape do
|
22
|
+
mirror_rake_tasks
|
23
|
+
end
|
24
|
+
"""
|
25
|
+
And a file named "config/deploy.rb" with:
|
26
|
+
"""
|
27
|
+
require 'cape'
|
28
|
+
|
29
|
+
Cape do
|
30
|
+
mirror_rake_tasks
|
31
|
+
end
|
32
|
+
"""
|
33
|
+
When I run `cap with_one_arg`
|
34
|
+
Then the output should contain:
|
35
|
+
"""
|
36
|
+
* executing `with_one_arg'
|
37
|
+
"""
|
38
|
+
And the output should contain "Environment variable THE_ARG must be set (RuntimeError)"
|
39
|
+
|
40
|
+
Scenario: invoke Rake task 'with_one_arg' with its argument
|
41
|
+
Given a full-featured Rakefile
|
42
|
+
And a file named "Capfile" with:
|
43
|
+
"""
|
44
|
+
load 'deploy' if respond_to?(:namespace) # cap2 differentiator
|
45
|
+
|
46
|
+
# Uncomment if you are using Rails' asset pipeline
|
47
|
+
# load 'deploy/assets'
|
48
|
+
|
49
|
+
Dir['vendor/gems/*/recipes/*.rb','vendor/plugins/*/recipes/*.rb'].each { |plugin| load(plugin) }
|
50
|
+
|
51
|
+
load 'config/deploy' # remove this line to skip loading any of the default tasks
|
52
|
+
require 'cape'
|
53
|
+
|
54
|
+
Cape do
|
55
|
+
mirror_rake_tasks
|
56
|
+
end
|
57
|
+
"""
|
58
|
+
And a file named "config/deploy.rb" with:
|
59
|
+
"""
|
60
|
+
require 'cape'
|
61
|
+
|
62
|
+
Cape do
|
63
|
+
mirror_rake_tasks
|
64
|
+
end
|
65
|
+
"""
|
66
|
+
When I run `cap with_one_arg THE_ARG="the arg goes here"`
|
67
|
+
Then the output should contain:
|
68
|
+
"""
|
69
|
+
* executing `with_one_arg'
|
70
|
+
"""
|
data/lib/cape.rb
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Dir.glob( File.expand_path( 'cape/*.rb', File.dirname( __FILE__ ))) do |f|
|
2
|
+
require "cape/#{File.basename f, '.rb'}"
|
3
|
+
end
|
4
|
+
|
5
|
+
# Contains the implementation of Cape.
|
6
|
+
module Cape
|
7
|
+
|
8
|
+
extend DSL
|
9
|
+
|
10
|
+
end
|
11
|
+
|
12
|
+
# The method used to group Cape statements in a block.
|
13
|
+
def Cape(&block)
|
14
|
+
Cape.module_eval do
|
15
|
+
@outer_self = block.binding.eval('self', __FILE__, __LINE__)
|
16
|
+
if 0 < block.arity
|
17
|
+
block.call self
|
18
|
+
else
|
19
|
+
module_eval(&block)
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,86 @@
|
|
1
|
+
require 'cape/strings'
|
2
|
+
|
3
|
+
module Cape
|
4
|
+
|
5
|
+
# An abstraction of the Capistrano installation.
|
6
|
+
class Capistrano
|
7
|
+
|
8
|
+
# Defines the specified _task_ as a Capistrano task, provided a Binding
|
9
|
+
# named argument +:binding+ and a Cape::Rake named argument +:rake+. Any
|
10
|
+
# parameters the task has are converted to environment variables, since
|
11
|
+
# Capistrano does not have the concept of task parameters.
|
12
|
+
#
|
13
|
+
# The _task_ argument must be a Hash of the form:
|
14
|
+
#
|
15
|
+
# {:name => <String>,
|
16
|
+
# :parameters => <String Array or nil>,
|
17
|
+
# :description => <String>}
|
18
|
+
def define(task, named_arguments={})
|
19
|
+
unless (binding = named_arguments[:binding])
|
20
|
+
raise ::ArgumentError, ':binding named argument is required'
|
21
|
+
end
|
22
|
+
unless (rake = named_arguments[:rake])
|
23
|
+
raise ::ArgumentError, ':rake named argument is required'
|
24
|
+
end
|
25
|
+
roles = named_arguments[:roles] || :app
|
26
|
+
|
27
|
+
capistrano = binding.eval('self', __FILE__, __LINE__)
|
28
|
+
if (description = build_capistrano_description(task))
|
29
|
+
capistrano.desc description
|
30
|
+
end
|
31
|
+
implement task, roles, capistrano, rake
|
32
|
+
self
|
33
|
+
end
|
34
|
+
|
35
|
+
private
|
36
|
+
|
37
|
+
def build_capistrano_description(task)
|
38
|
+
return nil unless task[:description]
|
39
|
+
|
40
|
+
description = [task[:description]]
|
41
|
+
description << '.' unless task[:description].end_with?('.')
|
42
|
+
unless (parameters = Array(task[:parameters])).empty?
|
43
|
+
noun = Strings.pluralize('variable', parameters.length)
|
44
|
+
parameters_list = Strings.to_list_phrase(parameters.collect(&:upcase))
|
45
|
+
description << <<-end_description
|
46
|
+
|
47
|
+
|
48
|
+
You must set environment #{noun} #{parameters_list}.
|
49
|
+
end_description
|
50
|
+
end
|
51
|
+
description.join
|
52
|
+
end
|
53
|
+
|
54
|
+
def implement(task, roles, capistrano_context, rake)
|
55
|
+
name = task[:name].split(':')
|
56
|
+
# Define the task.
|
57
|
+
block = lambda { |context|
|
58
|
+
context.task name.last, :roles => roles do
|
59
|
+
arguments = Array(task[:parameters]).collect do |a|
|
60
|
+
unless (value = ENV[a.upcase])
|
61
|
+
fail "Environment variable #{a.upcase} must be set"
|
62
|
+
end
|
63
|
+
value
|
64
|
+
end
|
65
|
+
if arguments.empty?
|
66
|
+
arguments = nil
|
67
|
+
else
|
68
|
+
arguments = "[#{arguments.join ','}]"
|
69
|
+
end
|
70
|
+
context.run "cd #{context.current_path} && " +
|
71
|
+
"#{rake.remote_executable} #{name.join ':'}#{arguments}"
|
72
|
+
end
|
73
|
+
}
|
74
|
+
# Nest the task inside its containing namespaces.
|
75
|
+
name[0...-1].reverse.each do |namespace_token|
|
76
|
+
inner_block = block
|
77
|
+
block = lambda { |context|
|
78
|
+
context.namespace(namespace_token, &inner_block)
|
79
|
+
}
|
80
|
+
end
|
81
|
+
block.call capistrano_context
|
82
|
+
end
|
83
|
+
|
84
|
+
end
|
85
|
+
|
86
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
module Cape
|
2
|
+
|
3
|
+
module CoreExt
|
4
|
+
|
5
|
+
# Contains extensions to the Hash core class.
|
6
|
+
module Hash
|
7
|
+
|
8
|
+
# Returns a copy of the Hash containing values only for the specified
|
9
|
+
# _keys_.
|
10
|
+
def slice(*keys)
|
11
|
+
::Hash[select { |key, value| keys.include? key }]
|
12
|
+
end
|
13
|
+
|
14
|
+
end
|
15
|
+
|
16
|
+
end
|
17
|
+
|
18
|
+
end
|
19
|
+
|
20
|
+
unless Hash.instance_methods.collect(&:to_s).include?('slice')
|
21
|
+
Hash.class_eval do
|
22
|
+
include Cape::CoreExt::Hash
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
module Cape
|
2
|
+
|
3
|
+
module CoreExt
|
4
|
+
|
5
|
+
# Contains extensions to the Symbol core class.
|
6
|
+
module Symbol
|
7
|
+
|
8
|
+
# Returns +0+ if the Symbol is equal to _other_, +-1+ if it is
|
9
|
+
# alphabetically lesser than _other_, and +1+ if it is alphabetically
|
10
|
+
# greater than _other_.
|
11
|
+
def <=>(other)
|
12
|
+
to_s <=> other.to_s
|
13
|
+
end
|
14
|
+
|
15
|
+
end
|
16
|
+
|
17
|
+
end
|
18
|
+
|
19
|
+
end
|
20
|
+
|
21
|
+
unless Symbol.instance_methods.collect(&:to_s).include?('<=>')
|
22
|
+
Symbol.class_eval do
|
23
|
+
include Cape::CoreExt::Symbol
|
24
|
+
end
|
25
|
+
end
|
data/lib/cape/dsl.rb
ADDED
@@ -0,0 +1,81 @@
|
|
1
|
+
require 'cape/capistrano'
|
2
|
+
require 'cape/rake'
|
3
|
+
|
4
|
+
module Cape
|
5
|
+
|
6
|
+
# Provides methods for integrating Capistrano and Rake.
|
7
|
+
module DSL
|
8
|
+
|
9
|
+
# Yields each available Rake task to a block. The optional _task_expression_
|
10
|
+
# argument limits the list to a single task or a namespace containing
|
11
|
+
# multiple tasks.
|
12
|
+
#
|
13
|
+
# Tasks are yielded as Hash objects of the form:
|
14
|
+
#
|
15
|
+
# {:name => <String>,
|
16
|
+
# :parameters => <String Array or nil>,
|
17
|
+
# :description => <String>}
|
18
|
+
def each_rake_task(task_expression=nil, &block)
|
19
|
+
rake.each_task(task_expression, &block)
|
20
|
+
self
|
21
|
+
end
|
22
|
+
|
23
|
+
# Returns the command used to run Rake on the local computer. Defaults to
|
24
|
+
# Rake::DEFAULT_EXECUTABLE.
|
25
|
+
def local_rake_executable
|
26
|
+
rake.local_executable
|
27
|
+
end
|
28
|
+
|
29
|
+
# Sets the command used to run Rake on the local computer.
|
30
|
+
def local_rake_executable=(value)
|
31
|
+
rake.local_executable = value
|
32
|
+
end
|
33
|
+
|
34
|
+
# Defines each available Rake task as a Capistrano task. Any
|
35
|
+
# parameters the tasks have are converted to environment variables, since
|
36
|
+
# Capistrano does not have the concept of task parameters. The optional
|
37
|
+
# _task_expression_ argument limits the list to a single task or a namespace
|
38
|
+
# containing multiple tasks.
|
39
|
+
def mirror_rake_tasks(task_expression=nil)
|
40
|
+
d = nil
|
41
|
+
rake.each_task task_expression do |t|
|
42
|
+
(d ||= deployment_library).define t, :binding => binding, :rake => rake
|
43
|
+
end
|
44
|
+
self
|
45
|
+
end
|
46
|
+
|
47
|
+
# Returns the command used to run Rake on remote computers. Defaults to
|
48
|
+
# Rake::DEFAULT_EXECUTABLE.
|
49
|
+
def remote_rake_executable
|
50
|
+
rake.remote_executable
|
51
|
+
end
|
52
|
+
|
53
|
+
# Sets the command used to run Rake on remote computers.
|
54
|
+
def remote_rake_executable=(value)
|
55
|
+
rake.remote_executable = value
|
56
|
+
end
|
57
|
+
|
58
|
+
private
|
59
|
+
|
60
|
+
def deployment_library
|
61
|
+
raise_unless_capistrano
|
62
|
+
Capistrano.new
|
63
|
+
end
|
64
|
+
|
65
|
+
def method_missing(method, *args, &block)
|
66
|
+
@outer_self.send(method, *args, &block)
|
67
|
+
end
|
68
|
+
|
69
|
+
def raise_unless_capistrano
|
70
|
+
if @outer_self.method(:task).owner.name !~ /^Capistrano::/
|
71
|
+
raise 'Use this in the context of Capistrano recipes'
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
def rake
|
76
|
+
@rake ||= Rake.new
|
77
|
+
end
|
78
|
+
|
79
|
+
end
|
80
|
+
|
81
|
+
end
|
data/lib/cape/rake.rb
ADDED
@@ -0,0 +1,60 @@
|
|
1
|
+
module Cape
|
2
|
+
|
3
|
+
# An abstraction of the Rake installation and available tasks.
|
4
|
+
class Rake
|
5
|
+
|
6
|
+
# The default command used to run Rake.
|
7
|
+
DEFAULT_EXECUTABLE = '/usr/bin/env rake'.freeze
|
8
|
+
|
9
|
+
# Sets the command used to run Rake on the local computer.
|
10
|
+
attr_writer :local_executable
|
11
|
+
|
12
|
+
# Sets the command used to run Rake on remote computers.
|
13
|
+
attr_writer :remote_executable
|
14
|
+
|
15
|
+
# Constructs a new Rake object with the specified _attributes_.
|
16
|
+
def initialize(attributes={})
|
17
|
+
attributes.each do |name, value|
|
18
|
+
send "#{name}=", value
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
# Yields each available Rake task to a block. The optional _task_expression_
|
23
|
+
# argument limits the list to a single task or a namespace containing
|
24
|
+
# multiple tasks.
|
25
|
+
#
|
26
|
+
# Tasks are yielded as Hash objects of the form:
|
27
|
+
#
|
28
|
+
# {:name => <String>,
|
29
|
+
# :parameters => <String Array or nil>,
|
30
|
+
# :description => <String>}
|
31
|
+
def each_task(task_expression=nil)
|
32
|
+
task_expression = " #{task_expression}" if task_expression
|
33
|
+
command = "#{local_executable} --tasks #{task_expression}"
|
34
|
+
`#{command}`.each_line do |l|
|
35
|
+
matches = l.chomp.match(/^rake (.+?)(?:\[(.+?)\])?\s+# (.+)/)
|
36
|
+
task = {}.tap do |t|
|
37
|
+
t[:name] = matches[1].strip
|
38
|
+
t[:parameters] = matches[2].split(',') if matches[2]
|
39
|
+
t[:description] = matches[3]
|
40
|
+
end
|
41
|
+
yield task
|
42
|
+
end
|
43
|
+
self
|
44
|
+
end
|
45
|
+
|
46
|
+
# Returns the command used to run Rake on the local computer. Defaults to
|
47
|
+
# DEFAULT_EXECUTABLE.
|
48
|
+
def local_executable
|
49
|
+
@local_executable ||= DEFAULT_EXECUTABLE
|
50
|
+
end
|
51
|
+
|
52
|
+
# Returns the command used to run Rake on remote computers. Defaults to
|
53
|
+
# DEFAULT_EXECUTABLE.
|
54
|
+
def remote_executable
|
55
|
+
@remote_executable ||= DEFAULT_EXECUTABLE
|
56
|
+
end
|
57
|
+
|
58
|
+
end
|
59
|
+
|
60
|
+
end
|