cape 1.1.0 → 1.2.0

Sign up to get free protection for your applications and to get access to all the features.
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