cape 1.1.0 → 1.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (32) hide show
  1. data/.gitignore +1 -0
  2. data/.travis.yml +1 -1
  3. data/Gemfile +11 -9
  4. data/Guardfile +17 -0
  5. data/History.markdown +10 -0
  6. data/MIT-LICENSE.markdown +1 -1
  7. data/README.markdown +58 -9
  8. data/Rakefile +1 -1
  9. data/cape.gemspec +13 -12
  10. data/features/dsl/each_rake_task/with_defined_namespace_argument.feature +8 -0
  11. data/features/dsl/each_rake_task/with_undefined_argument.feature +25 -1
  12. data/features/dsl/each_rake_task/without_arguments.feature +8 -0
  13. data/features/dsl/mirror_rake_tasks/inside_capistrano_namespace/with_defined_namespace_argument.feature +25 -0
  14. data/features/dsl/mirror_rake_tasks/inside_capistrano_namespace/without_arguments.feature +50 -4
  15. data/features/dsl/mirror_rake_tasks/with_defined_namespace_argument.feature +19 -0
  16. data/features/dsl/mirror_rake_tasks/with_defined_task_and_valid_options_arguments_and_environment_variables.feature +199 -0
  17. data/features/dsl/mirror_rake_tasks/with_valid_options_argument.feature +90 -0
  18. data/features/dsl/mirror_rake_tasks/without_arguments.feature +48 -5
  19. data/features/dsl/rake_executable.feature +10 -6
  20. data/features/step_definitions.rb +3 -0
  21. data/lib/cape/capistrano.rb +70 -40
  22. data/lib/cape/dsl.rb +76 -13
  23. data/lib/cape/rake.rb +30 -6
  24. data/lib/cape/version.rb +1 -1
  25. data/spec/cape/capistrano_spec.rb +16 -0
  26. data/spec/cape/dsl_spec.rb +108 -29
  27. data/spec/cape/rake_spec.rb +40 -0
  28. data/spec/cape/util_spec.rb +2 -2
  29. data/spec/cape/version_spec.rb +0 -1
  30. data/spec/cape_spec.rb +21 -3
  31. metadata +24 -18
  32. data/spec/cape/task_spec.rb +0 -0
@@ -21,6 +21,9 @@ Given 'a full-featured Rakefile' do
21
21
  desc 'My task with one argument'
22
22
  task :with_one_arg, [:the_arg]
23
23
 
24
+ desc 'A task that shadows a namespace'
25
+ task :my_namespace
26
+
24
27
  namespace :my_namespace do
25
28
  desc 'My task in a namespace'
26
29
  task :in_a_namespace
@@ -1,3 +1,4 @@
1
+ require 'cape/rake'
1
2
  require 'cape/util'
2
3
 
3
4
  module Cape
@@ -5,56 +6,66 @@ module Cape
5
6
  # An abstraction of the Capistrano installation.
6
7
  class Capistrano
7
8
 
8
- # Defines the specified _task_ as a Capistrano task.
9
+ # A Cape abstraction of the Rake installation.
10
+ attr_accessor :rake
11
+
12
+ # Constructs a new Capistrano object with the specified _attributes_.
13
+ def initialize(attributes={})
14
+ attributes.each do |name, value|
15
+ send "#{name}=", value
16
+ end
17
+ self.rake ||= Rake.new
18
+ end
19
+
20
+ # Defines a wrapper in Capistrano around the specified Rake _task_.
9
21
  #
10
- # @param [Hash] task metadata for a task
11
- # @param [Hash] named_arguments named arguments
22
+ # @param [Hash] task metadata for a Rake task
23
+ # @param [Hash] named_arguments named arguments, including options to pass to
24
+ # the Capistrano +task+ method
12
25
  #
13
- # @option task [String] :name the name of the task
14
- # @option task [Array of String, nil] :parameters the names of the task's
15
- # parameters, if any
16
- # @option task [String] :description documentation for the task
26
+ # @option task [String] :name the name of the Rake task
27
+ # @option task [Array of String, nil] :parameters the names of the Rake
28
+ # task's parameters, if any
29
+ # @option task [String] :description documentation for the Rake
30
+ # task
17
31
  #
18
- # @option named_arguments [Binding] :binding the Binding of your
19
- # Capistrano recipes
20
- # file
21
- # @option named_arguments [Rake] :rake a Cape abstraction of
22
- # the Rake installation
23
- # @option named_arguments [[Array of] Symbol] :roles the Capistrano role(s)
24
- # of remote computers
25
- # that will execute
26
- # _task_
32
+ # @option named_arguments [Binding] :binding the Binding of your
33
+ # Capistrano recipes
34
+ # file
35
+ #
36
+ # @yield [env] a block that defines environment variables for the Rake task;
37
+ # optional
38
+ # @yieldparam [Hash] env the environment variables to set before executing
39
+ # the Rake task
27
40
  #
28
41
  # @return [Capistrano] the object
29
42
  #
30
43
  # @raise [ArgumentError] +named_arguments[:binding]+ is missing
31
- # @raise [ArgumentError] +named_arguments[:rake]+ is missing
32
44
  #
33
- # @note Any parameters that the task has are integrated via environment variables, since Capistrano does not support task parameters per se.
34
- def define(task, named_arguments)
45
+ # @note Any parameters that the Rake task has are integrated via environment variables, since Capistrano does not support recipe parameters per se.
46
+ #
47
+ # @see http://github.com/capistrano/capistrano/blob/master/lib/capistrano/task_definition.rb#L15-L17 Valid Capistrano ‘task’ method options
48
+ def define_rake_wrapper(task, named_arguments, &block)
35
49
  unless (binding = named_arguments[:binding])
36
50
  raise ::ArgumentError, ':binding named argument is required'
37
51
  end
38
- unless (rake = named_arguments[:rake])
39
- raise ::ArgumentError, ':rake named argument is required'
40
- end
41
- roles = named_arguments[:roles] || :app
42
52
 
43
- capistrano = binding.eval('self', __FILE__, __LINE__)
44
- if (description = build_capistrano_description(task))
45
- capistrano.desc description
53
+ capistrano_context = binding.eval('self', __FILE__, __LINE__)
54
+ options = named_arguments.reject do |key, value|
55
+ key == :binding
46
56
  end
47
- implement task, roles, capistrano, rake
48
- self
57
+ describe( task, capistrano_context, options, &block)
58
+ implement(task, capistrano_context, options, &block)
49
59
  end
50
60
 
51
61
  private
52
62
 
53
- def build_capistrano_description(task)
63
+ def build_capistrano_description(task, options, &block)
54
64
  return nil unless task[:description]
55
65
 
56
66
  description = [task[:description]]
57
67
  description << '.' unless task[:description].end_with?('.')
68
+
58
69
  unless (parameters = Array(task[:parameters])).empty?
59
70
  noun = Util.pluralize('variable', parameters.length)
60
71
  parameters_list = Util.to_list_phrase(parameters.collect(&:upcase))
@@ -62,20 +73,30 @@ module Cape
62
73
  noun_phrase = (parameters.length == 1) ?
63
74
  "a #{singular}" :
64
75
  Util.pluralize(singular)
65
- description << <<-end_description
76
+ description << <<-end_fragment
66
77
 
67
78
 
68
- Set environment #{noun} #{parameters_list} to pass #{noun_phrase}.
69
- end_description
79
+ Set environment #{noun} #{parameters_list} if you want to pass #{noun_phrase}.
80
+ end_fragment
70
81
  end
82
+
71
83
  description.join
72
84
  end
73
85
 
74
- def implement(task, roles, capistrano_context, rake)
75
- name = task[:name].split(':')
76
- # Define the task.
86
+ def describe(task, capistrano_context, options, &block)
87
+ if (description = build_capistrano_description(task, options, &block))
88
+ capistrano_context.desc description
89
+ end
90
+ self
91
+ end
92
+
93
+ def implement(task, capistrano_context, options, &env_block)
94
+ name_tokens = task[:name].split(':')
95
+ name_tokens << 'default' if task[:default]
96
+ rake = self.rake
97
+ # Define the recipe.
77
98
  block = lambda { |context|
78
- context.task name.last, :roles => roles do
99
+ context.task name_tokens.last, options do
79
100
  arguments = Array(task[:parameters]).collect do |a|
80
101
  if (value = ENV[a.upcase])
81
102
  value = value.inspect
@@ -87,18 +108,27 @@ Set environment #{noun} #{parameters_list} to pass #{noun_phrase}.
87
108
  else
88
109
  arguments = "[#{arguments.join ','}]"
89
110
  end
90
- context.run "cd #{context.current_path} && " +
91
- "#{rake.remote_executable} #{name.join ':'}#{arguments}"
111
+ env_hash = {}
112
+ env_block.call(env_hash) if env_block
113
+ env_strings = env_hash.collect do |var_name, var_value|
114
+ "#{var_name}=#{var_value.inspect}"
115
+ end
116
+ env = env_strings.empty? ? nil : (' ' + env_strings.join(' '))
117
+ command = "cd #{context.current_path} && " +
118
+ "#{rake.remote_executable} " +
119
+ "#{task[:name]}#{arguments}#{env}"
120
+ context.run command
92
121
  end
93
122
  }
94
- # Nest the task inside its containing namespaces.
95
- name[0...-1].reverse.each do |namespace_token|
123
+ # Nest the recipe inside its containing namespaces.
124
+ name_tokens[0...-1].reverse.each do |namespace_token|
96
125
  inner_block = block
97
126
  block = lambda { |context|
98
127
  context.namespace(namespace_token, &inner_block)
99
128
  }
100
129
  end
101
130
  block.call capistrano_context
131
+ self
102
132
  end
103
133
 
104
134
  end
@@ -74,8 +74,8 @@ module Cape
74
74
  rake.local_executable = value
75
75
  end
76
76
 
77
- # Makes the use of a block parameter optional by forwarding non-Cape method
78
- # calls to the containing binding.
77
+ # Makes the use of a Cape block parameter optional by forwarding non-Cape
78
+ # method calls to the containing binding.
79
79
  #
80
80
  # @param [Symbol, String] method the method called
81
81
  # @param [Array] args the arguments passed to _method_
@@ -85,13 +85,54 @@ module Cape
85
85
  @outer_self.send(method, *args, &block)
86
86
  end
87
87
 
88
- # Defines Rake tasks as Capistrano tasks.
88
+ # Defines Rake tasks as Capistrano recipes.
89
89
  #
90
- # @param [String, Symbol] task_expression the full name of a Rake task or
91
- # namespace to filter; optional
92
- # @return [DSL] the object
90
+ # @overload mirror_rake_tasks(task_expression=nil)
91
+ # Defines Rake tasks as Capistrano recipes.
92
+ #
93
+ # @param [String, Symbol] task_expression the full name of a Rake task or
94
+ # namespace to filter; optional
95
+ #
96
+ # @yield [env] a block that defines environment variables for the Rake
97
+ # task; optional
98
+ # @yieldparam [Hash] env the environment variables to set before executing
99
+ # the Rake task
100
+ #
101
+ # @return [DSL] the object
102
+ #
103
+ # @overload mirror_rake_tasks(capistrano_task_options={})
104
+ # Defines all Rake tasks as Capistrano recipes with options.
105
+ #
106
+ # @param [Hash] capistrano_task_options options to pass to the Capistrano
107
+ # +task+ method; optional
108
+ #
109
+ # @yield [env] a block that defines environment variables for the Rake
110
+ # task; optional
111
+ # @yieldparam [Hash] env the environment variables to set before executing
112
+ # the Rake task
113
+ #
114
+ # @return [DSL] the object
115
+ #
116
+ # @overload mirror_rake_tasks(task_expression, capistrano_task_options={})
117
+ # Defines specific Rake tasks as Capistrano recipes with options.
118
+ #
119
+ # @param [String, Symbol] task_expression the full name of a Rake
120
+ # task or namespace to
121
+ # filter
122
+ # @param [Hash] capistrano_task_options options to pass to the
123
+ # Capistrano +task+ method;
124
+ # optional
125
+ #
126
+ # @yield [env] a block that defines environment variables for the Rake
127
+ # task; optional
128
+ # @yieldparam [Hash] env the environment variables to set before executing
129
+ # the Rake task
130
+ #
131
+ # @return [DSL] the object
93
132
  #
94
- # @note Any parameters that the Rake tasks have are integrated via environment variables, since Capistrano does not support task parameters per se.
133
+ # @note Any parameters that the Rake tasks have are integrated via environment variables, since Capistrano does not support recipe parameters per se.
134
+ #
135
+ # @see http://github.com/capistrano/capistrano/blob/master/lib/capistrano/task_definition.rb#L15-L17 Valid Capistrano ‘task’ method options
95
136
  #
96
137
  # @example Mirroring all Rake tasks
97
138
  # # config/deploy.rb
@@ -102,13 +143,24 @@ module Cape
102
143
  # mirror_rake_tasks
103
144
  # end
104
145
  #
105
- # @example Mirroring some Rake tasks
146
+ # @example Mirroring specific Rake tasks
147
+ # # config/deploy.rb
148
+ #
149
+ # require 'cape'
150
+ #
151
+ # Cape do
152
+ # mirror_rake_tasks 'log:clear'
153
+ # end
154
+ #
155
+ # @example Mirroring specific Rake tasks with Capistrano recipe options and/or environment variables
106
156
  # # config/deploy.rb
107
157
  #
108
158
  # require 'cape'
109
159
  #
110
160
  # Cape do
111
- # mirror_rake_tasks :foo
161
+ # mirror_rake_tasks :db, :roles => :app do |env|
162
+ # env['RAILS_ENV'] = rails_env
163
+ # end
112
164
  # end
113
165
  #
114
166
  # @example Mirroring Rake tasks into a Capistrano namespace
@@ -121,10 +173,19 @@ module Cape
121
173
  # cape.mirror_rake_tasks
122
174
  # end
123
175
  # end
124
- def mirror_rake_tasks(task_expression=nil)
125
- d = nil
176
+ def mirror_rake_tasks(*arguments, &block)
177
+ arguments_count = arguments.length
178
+ options = arguments.last.is_a?(Hash) ? arguments.pop.dup : {}
179
+ unless arguments.length <= 1
180
+ raise ::ArgumentError,
181
+ "wrong number of arguments (#{arguments_count} for 0 or 1, plus " +
182
+ 'an options hash)'
183
+ end
184
+
185
+ task_expression = arguments.first
186
+ options[:binding] = binding
126
187
  rake.each_task task_expression do |t|
127
- (d ||= deployment_library).define t, :binding => binding, :rake => rake
188
+ deployment_library.define_rake_wrapper(t, options, &block)
128
189
  end
129
190
  self
130
191
  end
@@ -157,8 +218,10 @@ module Cape
157
218
  private
158
219
 
159
220
  def deployment_library
221
+ return @deployment_library if @deployment_library
222
+
160
223
  raise_unless_capistrano
161
- Capistrano.new
224
+ @deployment_library = Capistrano.new(:rake => rake)
162
225
  end
163
226
 
164
227
  def raise_unless_capistrano
@@ -25,6 +25,17 @@ module Cape
25
25
  end
26
26
  end
27
27
 
28
+ # Compares the Rake object to another.
29
+ #
30
+ # @param [Object] other another object
31
+ # @return [true] the Rake object is equal to _other_
32
+ # @return [false] the Rake object is unequal to _other_
33
+ def ==(other)
34
+ other.kind_of?(Rake) &&
35
+ (other.local_executable == local_executable) &&
36
+ (other.remote_executable == remote_executable)
37
+ end
38
+
28
39
  # Enumerates Rake tasks.
29
40
  #
30
41
  # @param [String, Symbol] task_expression the full name of a task or
@@ -36,17 +47,30 @@ module Cape
36
47
  #
37
48
  # @return [Rake] the object
38
49
  def each_task(task_expression=nil)
39
- task_expression = " #{task_expression}" if task_expression
40
- command = "#{local_executable} --tasks #{task_expression}"
41
- `#{command}`.each_line do |l|
42
- matches = l.chomp.match(/^rake (.+?)(?:\[(.+?)\])?\s+# (.+)/)
43
- task = {}.tap do |t|
50
+ previous_task, this_task = nil, nil
51
+ task_expression = task_expression ?
52
+ ::Regexp.escape(task_expression.to_s) :
53
+ '.+?'
54
+ regexp = /^rake (#{task_expression}(?::.+?)?)(?:\[(.+?)\])?\s+# (.+)/
55
+ `#{local_executable} --tasks 2> /dev/null`.each_line do |l|
56
+ unless (matches = l.chomp.match(regexp))
57
+ next
58
+ end
59
+
60
+ previous_task = this_task
61
+ this_task = {}.tap do |t|
44
62
  t[:name] = matches[1].strip
45
63
  t[:parameters] = matches[2].split(',') if matches[2]
46
64
  t[:description] = matches[3]
47
65
  end
48
- yield task
66
+ if previous_task
67
+ all_but_last_segment = this_task[:name].split(':')[0...-1].join(':')
68
+ previous_task[:default] = (all_but_last_segment ==
69
+ previous_task[:name])
70
+ yield previous_task
71
+ end
49
72
  end
73
+ yield this_task if this_task
50
74
  self
51
75
  end
52
76
 
@@ -1,6 +1,6 @@
1
1
  module Cape
2
2
 
3
3
  # The version of Cape.
4
- VERSION = '1.1.0'
4
+ VERSION = '1.2.0'
5
5
 
6
6
  end
@@ -0,0 +1,16 @@
1
+ require 'cape/capistrano'
2
+ require 'cape/rake'
3
+
4
+ describe Cape::Capistrano do
5
+ describe 'without specified attributes' do
6
+ its(:rake) { should == Cape::Rake.new }
7
+ end
8
+
9
+ describe 'with specified attributes' do
10
+ subject {
11
+ described_class.new :rake => 'I see you have the machine that goes "Bing!"'
12
+ }
13
+
14
+ its(:rake) { should == 'I see you have the machine that goes "Bing!"' }
15
+ end
16
+ end
@@ -19,7 +19,7 @@ describe Cape::DSL do
19
19
 
20
20
  before :each do
21
21
  Cape::Capistrano.stub!(:new).and_return mock_capistrano
22
- Cape::Rake.stub!(:new).and_return mock_rake
22
+ Cape::Rake. stub!(:new).and_return mock_rake
23
23
  end
24
24
 
25
25
  describe 'when sent #each_rake_task' do
@@ -47,52 +47,131 @@ describe Cape::DSL do
47
47
  subject.stub! :raise_unless_capistrano
48
48
  end
49
49
 
50
- def do_mirror_rake_tasks
51
- subject.mirror_rake_tasks task_expression
50
+ def do_mirror_rake_tasks(*arguments, &block)
51
+ subject.mirror_rake_tasks(*arguments, &block)
52
52
  end
53
53
 
54
- it 'should collect Rake#each_task' do
55
- mock_rake.should_receive(:each_task).with task_expression
56
- do_mirror_rake_tasks
54
+ describe 'with two scalar arguments' do
55
+ specify do
56
+ lambda {
57
+ do_mirror_rake_tasks task_expression, task_expression
58
+ }.should raise_error(ArgumentError,
59
+ 'wrong number of arguments (2 for 0 or 1, plus ' +
60
+ 'an options hash)')
61
+ end
57
62
  end
58
63
 
59
- it 'should verify that Capistrano is in use' do
60
- subject.should_receive :raise_unless_capistrano
61
- do_mirror_rake_tasks
62
- end
64
+ shared_examples_for 'a successful call' do |task_expression_in_use,
65
+ options_in_use|
66
+ it 'should collect Rake#each_task' do
67
+ mock_rake.should_receive(:each_task).with task_expression_in_use
68
+ do_mirror_rake_tasks
69
+ end
63
70
 
64
- describe 'should Capistrano#define Rake#each_task' do
65
- it 'with the expected task' do
66
- mock_capistrano.should_receive(:define).with do |task, options|
67
- task == {:name => task_expression}
68
- end
71
+ it 'should verify that Capistrano is in use' do
72
+ subject.should_receive :raise_unless_capistrano
69
73
  do_mirror_rake_tasks
70
74
  end
71
75
 
72
- it 'with the expected named options' do
73
- mock_capistrano.should_receive(:define).with do |task, opts|
74
- opts.keys.sort == [:binding, :rake]
76
+ describe 'should Capistrano#define_rake_wrapper for Rake#each_task' do
77
+ specify 'with the expected task' do
78
+ mock_capistrano.should_receive(:define_rake_wrapper).with do |task,
79
+ named_arguments|
80
+ task == {:name => task_expression}
81
+ end
82
+ do_mirror_rake_tasks
83
+ end
84
+
85
+ specify 'with the expected named options' do
86
+ mock_capistrano.should_receive(:define_rake_wrapper).with do |task,
87
+ named_arguments|
88
+ named_arguments.keys.sort == ([:binding] + options_in_use.keys).sort
89
+ end
90
+ do_mirror_rake_tasks
91
+ end
92
+
93
+ specify 'with a :binding option' do
94
+ mock_capistrano.should_receive(:define_rake_wrapper).with do |task,
95
+ named_arguments|
96
+ named_arguments[:binding].is_a? Binding
97
+ end
98
+ do_mirror_rake_tasks
99
+ end
100
+
101
+ specify 'with any provided extra options' do
102
+ mock_capistrano.should_receive(:define_rake_wrapper).with do |task,
103
+ named_arguments|
104
+ named_arguments.slice(*options_in_use.keys) == options_in_use
105
+ end
106
+ do_mirror_rake_tasks
75
107
  end
76
- do_mirror_rake_tasks
77
108
  end
78
109
 
79
- it 'with a :binding option' do
80
- mock_capistrano.should_receive(:define).with do |task, options|
81
- options[:binding].is_a? Binding
110
+ it 'should return itself' do
111
+ do_mirror_rake_tasks.should == subject
112
+ end
113
+ end
114
+
115
+ describe 'with one scalar argument, an options hash, and a block' do
116
+ def do_mirror_rake_tasks
117
+ super 'task:expression', :bar => :baz do |env|
118
+ env['AN_ENV_VAR'] = 'an environment variable'
82
119
  end
83
- do_mirror_rake_tasks
84
120
  end
85
121
 
86
- it 'with the expected :rake option' do
87
- mock_capistrano.should_receive(:define).with do |task, options|
88
- options[:rake] == mock_rake
122
+ it_should_behave_like 'a successful call', 'task:expression', :bar => :baz
123
+ end
124
+
125
+ describe 'with one scalar argument and an options hash' do
126
+ def do_mirror_rake_tasks
127
+ super 'task:expression', :bar => :baz
128
+ end
129
+
130
+ it_should_behave_like 'a successful call', 'task:expression', :bar => :baz
131
+ end
132
+
133
+ describe 'with an options hash and a block' do
134
+ def do_mirror_rake_tasks
135
+ super :bar => :baz do |env|
136
+ env['AN_ENV_VAR'] = 'an environment variable'
89
137
  end
90
- do_mirror_rake_tasks
91
138
  end
139
+
140
+ it_should_behave_like 'a successful call', nil, :bar => :baz
92
141
  end
93
142
 
94
- it 'should return itself' do
95
- do_mirror_rake_tasks.should == subject
143
+ describe 'with an options hash' do
144
+ def do_mirror_rake_tasks
145
+ super :bar => :baz
146
+ end
147
+
148
+ it_should_behave_like 'a successful call', nil, :bar => :baz
149
+ end
150
+
151
+ describe 'with one scalar argument and a block' do
152
+ def do_mirror_rake_tasks
153
+ super 'task:expression' do |env|
154
+ env['AN_ENV_VAR'] = 'an environment variable'
155
+ end
156
+ end
157
+
158
+ it_should_behave_like('a successful call', 'task:expression', {})
159
+ end
160
+
161
+ describe 'with one scalar argument' do
162
+ def do_mirror_rake_tasks
163
+ super 'task:expression'
164
+ end
165
+
166
+ it_should_behave_like('a successful call', 'task:expression', {})
167
+ end
168
+
169
+ describe 'without arguments' do
170
+ def do_mirror_rake_tasks
171
+ super
172
+ end
173
+
174
+ it_should_behave_like('a successful call', nil, {})
96
175
  end
97
176
  end
98
177